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))
170 class ConfigWriter(object):
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 (recursively, including children).
452 def DiskAndAllChildren(disk):
453 """Returns a list containing the given disk and all of his children.
458 for child_disk in disk.children:
459 disks.extend(DiskAndAllChildren(child_disk))
463 for instance in self._config_data.instances.values():
464 for disk in instance.disks:
465 disks.extend(DiskAndAllChildren(disk))
469 """Compute the list of all NICs.
473 for instance in self._config_data.instances.values():
474 nics.extend(instance.nics)
477 def _AllIDs(self, include_temporary):
478 """Compute the list of all UUIDs and names we have.
480 @type include_temporary: boolean
481 @param include_temporary: whether to include the _temporary_ids set
483 @return: a set of IDs
487 if include_temporary:
488 existing.update(self._temporary_ids.GetReserved())
489 existing.update(self._AllLVs())
490 existing.update(self._config_data.instances.keys())
491 existing.update(self._config_data.nodes.keys())
492 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
495 def _GenerateUniqueID(self, ec_id):
496 """Generate an unique UUID.
498 This checks the current node, instances and disk names for
502 @return: the unique id
505 existing = self._AllIDs(include_temporary=False)
506 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
508 @locking.ssynchronized(_config_lock, shared=1)
509 def GenerateUniqueID(self, ec_id):
510 """Generate an unique ID.
512 This is just a wrapper over the unlocked version.
515 @param ec_id: unique id for the job to reserve the id to
518 return self._GenerateUniqueID(ec_id)
521 """Return all MACs present in the config.
524 @return: the list of all MACs
528 for instance in self._config_data.instances.values():
529 for nic in instance.nics:
530 result.append(nic.mac)
534 def _AllDRBDSecrets(self):
535 """Return all DRBD secrets present in the config.
538 @return: the list of all DRBD secrets
541 def helper(disk, result):
542 """Recursively gather secrets from this disk."""
543 if disk.dev_type == constants.DT_DRBD8:
544 result.append(disk.logical_id[5])
546 for child in disk.children:
547 helper(child, result)
550 for instance in self._config_data.instances.values():
551 for disk in instance.disks:
556 def _CheckDiskIDs(self, disk, l_ids):
557 """Compute duplicate disk IDs
559 @type disk: L{objects.Disk}
560 @param disk: the disk at which to start searching
562 @param l_ids: list of current logical ids
564 @return: a list of error messages
568 if disk.logical_id is not None:
569 if disk.logical_id in l_ids:
570 result.append("duplicate logical id %s" % str(disk.logical_id))
572 l_ids.append(disk.logical_id)
575 for child in disk.children:
576 result.extend(self._CheckDiskIDs(child, l_ids))
579 def _UnlockedVerifyConfig(self):
583 @return: a list of error messages; a non-empty list signifies
587 # pylint: disable=R0914
591 data = self._config_data
592 cluster = data.cluster
595 # global cluster checks
596 if not cluster.enabled_hypervisors:
597 result.append("enabled hypervisors list doesn't have any entries")
598 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
600 result.append("enabled hypervisors contains invalid entries: %s" %
601 utils.CommaJoin(invalid_hvs))
602 missing_hvp = (set(cluster.enabled_hypervisors) -
603 set(cluster.hvparams.keys()))
605 result.append("hypervisor parameters missing for the enabled"
606 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
608 if not cluster.enabled_disk_templates:
609 result.append("enabled disk templates list doesn't have any entries")
610 invalid_disk_templates = set(cluster.enabled_disk_templates) \
611 - constants.DISK_TEMPLATES
612 if invalid_disk_templates:
613 result.append("enabled disk templates list contains invalid entries:"
614 " %s" % utils.CommaJoin(invalid_disk_templates))
616 if cluster.master_node not in data.nodes:
617 result.append("cluster has invalid primary node '%s'" %
620 def _helper(owner, attr, value, template):
622 utils.ForceDictType(value, template)
623 except errors.GenericError, err:
624 result.append("%s has invalid %s: %s" % (owner, attr, err))
626 def _helper_nic(owner, params):
628 objects.NIC.CheckParameterSyntax(params)
629 except errors.ConfigurationError, err:
630 result.append("%s has invalid nicparams: %s" % (owner, err))
632 def _helper_ipolicy(owner, ipolicy, iscluster):
634 objects.InstancePolicy.CheckParameterSyntax(ipolicy, iscluster)
635 except errors.ConfigurationError, err:
636 result.append("%s has invalid instance policy: %s" % (owner, err))
637 for key, value in ipolicy.items():
638 if key == constants.ISPECS_MINMAX:
639 for k in range(len(value)):
640 _helper_ispecs(owner, "ipolicy/%s[%s]" % (key, k), value[k])
641 elif key == constants.ISPECS_STD:
642 _helper(owner, "ipolicy/" + key, value,
643 constants.ISPECS_PARAMETER_TYPES)
645 # FIXME: assuming list type
646 if key in constants.IPOLICY_PARAMETERS:
650 if not isinstance(value, exp_type):
651 result.append("%s has invalid instance policy: for %s,"
652 " expecting %s, got %s" %
653 (owner, key, exp_type.__name__, type(value)))
655 def _helper_ispecs(owner, parentkey, params):
656 for (key, value) in params.items():
657 fullkey = "/".join([parentkey, key])
658 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
660 # check cluster parameters
661 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
662 constants.BES_PARAMETER_TYPES)
663 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
664 constants.NICS_PARAMETER_TYPES)
665 _helper_nic("cluster", cluster.SimpleFillNIC({}))
666 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
667 constants.NDS_PARAMETER_TYPES)
668 _helper_ipolicy("cluster", cluster.ipolicy, True)
670 # per-instance checks
671 for instance_uuid in data.instances:
672 instance = data.instances[instance_uuid]
673 if instance.uuid != instance_uuid:
674 result.append("instance '%s' is indexed by wrong UUID '%s'" %
675 (instance.name, instance_uuid))
676 if instance.primary_node not in data.nodes:
677 result.append("instance '%s' has invalid primary node '%s'" %
678 (instance.name, instance.primary_node))
679 for snode in instance.secondary_nodes:
680 if snode not in data.nodes:
681 result.append("instance '%s' has invalid secondary node '%s'" %
682 (instance.name, snode))
683 for idx, nic in enumerate(instance.nics):
684 if nic.mac in seen_macs:
685 result.append("instance '%s' has NIC %d mac %s duplicate" %
686 (instance.name, idx, nic.mac))
688 seen_macs.append(nic.mac)
690 filled = cluster.SimpleFillNIC(nic.nicparams)
691 owner = "instance %s nic %d" % (instance.name, idx)
692 _helper(owner, "nicparams",
693 filled, constants.NICS_PARAMETER_TYPES)
694 _helper_nic(owner, filled)
696 # disk template checks
697 if not instance.disk_template in data.cluster.enabled_disk_templates:
698 result.append("instance '%s' uses the disabled disk template '%s'." %
699 (instance.name, instance.disk_template))
702 if instance.beparams:
703 _helper("instance %s" % instance.name, "beparams",
704 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
706 # gather the drbd ports for duplicate checks
707 for (idx, dsk) in enumerate(instance.disks):
708 if dsk.dev_type in constants.LDS_DRBD:
709 tcp_port = dsk.logical_id[2]
710 if tcp_port not in ports:
712 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
713 # gather network port reservation
714 net_port = getattr(instance, "network_port", None)
715 if net_port is not None:
716 if net_port not in ports:
718 ports[net_port].append((instance.name, "network port"))
720 # instance disk verify
721 for idx, disk in enumerate(instance.disks):
722 result.extend(["instance '%s' disk %d error: %s" %
723 (instance.name, idx, msg) for msg in disk.Verify()])
724 result.extend(self._CheckDiskIDs(disk, seen_lids))
726 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
728 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
729 (idx, exp_name, actual_name))
730 for (idx, exp_name, actual_name) in wrong_names)
732 result.append("Instance '%s' has wrongly named disks: %s" %
733 (instance.name, tmp))
735 # cluster-wide pool of free ports
736 for free_port in cluster.tcpudp_port_pool:
737 if free_port not in ports:
738 ports[free_port] = []
739 ports[free_port].append(("cluster", "port marked as free"))
741 # compute tcp/udp duplicate ports
747 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
748 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
750 # highest used tcp port check
752 if keys[-1] > cluster.highest_used_port:
753 result.append("Highest used port mismatch, saved %s, computed %s" %
754 (cluster.highest_used_port, keys[-1]))
756 if not data.nodes[cluster.master_node].master_candidate:
757 result.append("Master node is not a master candidate")
759 # master candidate checks
760 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
762 result.append("Not enough master candidates: actual %d, target %d" %
766 for node_uuid, node in data.nodes.items():
767 if node.uuid != node_uuid:
768 result.append("Node '%s' is indexed by wrong UUID '%s'" %
769 (node.name, node_uuid))
770 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
771 result.append("Node %s state is invalid: master_candidate=%s,"
772 " drain=%s, offline=%s" %
773 (node.name, node.master_candidate, node.drained,
775 if node.group not in data.nodegroups:
776 result.append("Node '%s' has invalid group '%s'" %
777 (node.name, node.group))
779 _helper("node %s" % node.name, "ndparams",
780 cluster.FillND(node, data.nodegroups[node.group]),
781 constants.NDS_PARAMETER_TYPES)
782 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
784 result.append("Node '%s' has some global parameters set: %s" %
785 (node.name, utils.CommaJoin(used_globals)))
788 nodegroups_names = set()
789 for nodegroup_uuid in data.nodegroups:
790 nodegroup = data.nodegroups[nodegroup_uuid]
791 if nodegroup.uuid != nodegroup_uuid:
792 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
793 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
794 if utils.UUID_RE.match(nodegroup.name.lower()):
795 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
796 (nodegroup.name, nodegroup.uuid))
797 if nodegroup.name in nodegroups_names:
798 result.append("duplicate node group name '%s'" % nodegroup.name)
800 nodegroups_names.add(nodegroup.name)
801 group_name = "group %s" % nodegroup.name
802 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
804 if nodegroup.ndparams:
805 _helper(group_name, "ndparams",
806 cluster.SimpleFillND(nodegroup.ndparams),
807 constants.NDS_PARAMETER_TYPES)
810 _, duplicates = self._UnlockedComputeDRBDMap()
811 for node, minor, instance_a, instance_b in duplicates:
812 result.append("DRBD minor %d on node %s is assigned twice to instances"
813 " %s and %s" % (minor, node, instance_a, instance_b))
816 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
819 def _AddIpAddress(ip, name):
820 ips.setdefault(ip, []).append(name)
822 _AddIpAddress(cluster.master_ip, "cluster_ip")
824 for node in data.nodes.values():
825 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
826 if node.secondary_ip != node.primary_ip:
827 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
829 for instance in data.instances.values():
830 for idx, nic in enumerate(instance.nics):
834 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
835 nic_mode = nicparams[constants.NIC_MODE]
836 nic_link = nicparams[constants.NIC_LINK]
838 if nic_mode == constants.NIC_MODE_BRIDGED:
839 link = "bridge:%s" % nic_link
840 elif nic_mode == constants.NIC_MODE_ROUTED:
841 link = "route:%s" % nic_link
843 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
845 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
846 "instance:%s/nic:%d" % (instance.name, idx))
848 for ip, owners in ips.items():
850 result.append("IP address %s is used by multiple owners: %s" %
851 (ip, utils.CommaJoin(owners)))
855 @locking.ssynchronized(_config_lock, shared=1)
856 def VerifyConfig(self):
859 This is just a wrapper over L{_UnlockedVerifyConfig}.
862 @return: a list of error messages; a non-empty list signifies
866 return self._UnlockedVerifyConfig()
868 @locking.ssynchronized(_config_lock)
869 def AddTcpUdpPort(self, port):
870 """Adds a new port to the available port pool.
872 @warning: this method does not "flush" the configuration (via
873 L{_WriteConfig}); callers should do that themselves once the
874 configuration is stable
877 if not isinstance(port, int):
878 raise errors.ProgrammerError("Invalid type passed for port")
880 self._config_data.cluster.tcpudp_port_pool.add(port)
882 @locking.ssynchronized(_config_lock, shared=1)
883 def GetPortList(self):
884 """Returns a copy of the current port list.
887 return self._config_data.cluster.tcpudp_port_pool.copy()
889 @locking.ssynchronized(_config_lock)
890 def AllocatePort(self):
893 The port will be taken from the available port pool or from the
894 default port range (and in this case we increase
898 # If there are TCP/IP ports configured, we use them first.
899 if self._config_data.cluster.tcpudp_port_pool:
900 port = self._config_data.cluster.tcpudp_port_pool.pop()
902 port = self._config_data.cluster.highest_used_port + 1
903 if port >= constants.LAST_DRBD_PORT:
904 raise errors.ConfigurationError("The highest used port is greater"
905 " than %s. Aborting." %
906 constants.LAST_DRBD_PORT)
907 self._config_data.cluster.highest_used_port = port
912 def _UnlockedComputeDRBDMap(self):
913 """Compute the used DRBD minor/nodes.
916 @return: dictionary of node_uuid: dict of minor: instance_uuid;
917 the returned dict will have all the nodes in it (even if with
918 an empty list), and a list of duplicates; if the duplicates
919 list is not empty, the configuration is corrupted and its caller
920 should raise an exception
923 def _AppendUsedMinors(get_node_name_fn, instance, disk, used):
925 if disk.dev_type == constants.DT_DRBD8 and len(disk.logical_id) >= 5:
926 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
927 for node_uuid, minor in ((node_a, minor_a), (node_b, minor_b)):
928 assert node_uuid in used, \
929 ("Node '%s' of instance '%s' not found in node list" %
930 (get_node_name_fn(node_uuid), instance.name))
931 if minor in used[node_uuid]:
932 duplicates.append((node_uuid, minor, instance.uuid,
933 used[node_uuid][minor]))
935 used[node_uuid][minor] = instance.uuid
937 for child in disk.children:
938 duplicates.extend(_AppendUsedMinors(get_node_name_fn, instance, child,
943 my_dict = dict((node_uuid, {}) for node_uuid in self._config_data.nodes)
944 for instance in self._config_data.instances.itervalues():
945 for disk in instance.disks:
946 duplicates.extend(_AppendUsedMinors(self._UnlockedGetNodeName,
947 instance, disk, my_dict))
948 for (node_uuid, minor), inst_uuid in self._temporary_drbds.iteritems():
949 if minor in my_dict[node_uuid] and my_dict[node_uuid][minor] != inst_uuid:
950 duplicates.append((node_uuid, minor, inst_uuid,
951 my_dict[node_uuid][minor]))
953 my_dict[node_uuid][minor] = inst_uuid
954 return my_dict, duplicates
956 @locking.ssynchronized(_config_lock)
957 def ComputeDRBDMap(self):
958 """Compute the used DRBD minor/nodes.
960 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
962 @return: dictionary of node_uuid: dict of minor: instance_uuid;
963 the returned dict will have all the nodes in it (even if with
967 d_map, duplicates = self._UnlockedComputeDRBDMap()
969 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
973 @locking.ssynchronized(_config_lock)
974 def AllocateDRBDMinor(self, node_uuids, inst_uuid):
975 """Allocate a drbd minor.
977 The free minor will be automatically computed from the existing
978 devices. A node can be given multiple times in order to allocate
979 multiple minors. The result is the list of minors, in the same
980 order as the passed nodes.
982 @type inst_uuid: string
983 @param inst_uuid: the instance for which we allocate minors
986 assert isinstance(inst_uuid, basestring), \
987 "Invalid argument '%s' passed to AllocateDRBDMinor" % inst_uuid
989 d_map, duplicates = self._UnlockedComputeDRBDMap()
991 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
994 for nuuid in node_uuids:
997 # no minors used, we can start at 0
1000 self._temporary_drbds[(nuuid, 0)] = inst_uuid
1004 ffree = utils.FirstFree(keys)
1006 # return the next minor
1007 # TODO: implement high-limit check
1008 minor = keys[-1] + 1
1011 # double-check minor against current instances
1012 assert minor not in d_map[nuuid], \
1013 ("Attempt to reuse allocated DRBD minor %d on node %s,"
1014 " already allocated to instance %s" %
1015 (minor, nuuid, d_map[nuuid][minor]))
1016 ndata[minor] = inst_uuid
1017 # double-check minor against reservation
1018 r_key = (nuuid, minor)
1019 assert r_key not in self._temporary_drbds, \
1020 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1021 " reserved for instance %s" %
1022 (minor, nuuid, self._temporary_drbds[r_key]))
1023 self._temporary_drbds[r_key] = inst_uuid
1024 result.append(minor)
1025 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1029 def _UnlockedReleaseDRBDMinors(self, inst_uuid):
1030 """Release temporary drbd minors allocated for a given instance.
1032 @type inst_uuid: string
1033 @param inst_uuid: the instance for which temporary minors should be
1037 assert isinstance(inst_uuid, basestring), \
1038 "Invalid argument passed to ReleaseDRBDMinors"
1039 for key, uuid in self._temporary_drbds.items():
1040 if uuid == inst_uuid:
1041 del self._temporary_drbds[key]
1043 @locking.ssynchronized(_config_lock)
1044 def ReleaseDRBDMinors(self, inst_uuid):
1045 """Release temporary drbd minors allocated for a given instance.
1047 This should be called on the error paths, on the success paths
1048 it's automatically called by the ConfigWriter add and update
1051 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1053 @type inst_uuid: string
1054 @param inst_uuid: the instance for which temporary minors should be
1058 self._UnlockedReleaseDRBDMinors(inst_uuid)
1060 @locking.ssynchronized(_config_lock, shared=1)
1061 def GetConfigVersion(self):
1062 """Get the configuration version.
1064 @return: Config version
1067 return self._config_data.version
1069 @locking.ssynchronized(_config_lock, shared=1)
1070 def GetClusterName(self):
1071 """Get cluster name.
1073 @return: Cluster name
1076 return self._config_data.cluster.cluster_name
1078 @locking.ssynchronized(_config_lock, shared=1)
1079 def GetMasterNode(self):
1080 """Get the UUID of the master node for this cluster.
1082 @return: Master node UUID
1085 return self._config_data.cluster.master_node
1087 @locking.ssynchronized(_config_lock, shared=1)
1088 def GetMasterNodeName(self):
1089 """Get the hostname of the master node for this cluster.
1091 @return: Master node hostname
1094 return self._UnlockedGetNodeName(self._config_data.cluster.master_node)
1096 @locking.ssynchronized(_config_lock, shared=1)
1097 def GetMasterNodeInfo(self):
1098 """Get the master node information for this cluster.
1100 @rtype: objects.Node
1101 @return: Master node L{objects.Node} object
1104 return self._UnlockedGetNodeInfo(self._config_data.cluster.master_node)
1106 @locking.ssynchronized(_config_lock, shared=1)
1107 def GetMasterIP(self):
1108 """Get the IP of the master node for this cluster.
1113 return self._config_data.cluster.master_ip
1115 @locking.ssynchronized(_config_lock, shared=1)
1116 def GetMasterNetdev(self):
1117 """Get the master network device for this cluster.
1120 return self._config_data.cluster.master_netdev
1122 @locking.ssynchronized(_config_lock, shared=1)
1123 def GetMasterNetmask(self):
1124 """Get the netmask of the master node for this cluster.
1127 return self._config_data.cluster.master_netmask
1129 @locking.ssynchronized(_config_lock, shared=1)
1130 def GetUseExternalMipScript(self):
1131 """Get flag representing whether to use the external master IP setup script.
1134 return self._config_data.cluster.use_external_mip_script
1136 @locking.ssynchronized(_config_lock, shared=1)
1137 def GetFileStorageDir(self):
1138 """Get the file storage dir for this cluster.
1141 return self._config_data.cluster.file_storage_dir
1143 @locking.ssynchronized(_config_lock, shared=1)
1144 def GetSharedFileStorageDir(self):
1145 """Get the shared file storage dir for this cluster.
1148 return self._config_data.cluster.shared_file_storage_dir
1150 @locking.ssynchronized(_config_lock, shared=1)
1151 def GetHypervisorType(self):
1152 """Get the hypervisor type for this cluster.
1155 return self._config_data.cluster.enabled_hypervisors[0]
1157 @locking.ssynchronized(_config_lock, shared=1)
1158 def GetRsaHostKey(self):
1159 """Return the rsa hostkey from the config.
1162 @return: the rsa hostkey
1165 return self._config_data.cluster.rsahostkeypub
1167 @locking.ssynchronized(_config_lock, shared=1)
1168 def GetDsaHostKey(self):
1169 """Return the dsa hostkey from the config.
1172 @return: the dsa hostkey
1175 return self._config_data.cluster.dsahostkeypub
1177 @locking.ssynchronized(_config_lock, shared=1)
1178 def GetDefaultIAllocator(self):
1179 """Get the default instance allocator for this cluster.
1182 return self._config_data.cluster.default_iallocator
1184 @locking.ssynchronized(_config_lock, shared=1)
1185 def GetPrimaryIPFamily(self):
1186 """Get cluster primary ip family.
1188 @return: primary ip family
1191 return self._config_data.cluster.primary_ip_family
1193 @locking.ssynchronized(_config_lock, shared=1)
1194 def GetMasterNetworkParameters(self):
1195 """Get network parameters of the master node.
1197 @rtype: L{object.MasterNetworkParameters}
1198 @return: network parameters of the master node
1201 cluster = self._config_data.cluster
1202 result = objects.MasterNetworkParameters(
1203 uuid=cluster.master_node, ip=cluster.master_ip,
1204 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1205 ip_family=cluster.primary_ip_family)
1209 @locking.ssynchronized(_config_lock)
1210 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1211 """Add a node group to the configuration.
1213 This method calls group.UpgradeConfig() to fill any missing attributes
1214 according to their default values.
1216 @type group: L{objects.NodeGroup}
1217 @param group: the NodeGroup object to add
1219 @param ec_id: unique id for the job to use when creating a missing UUID
1220 @type check_uuid: bool
1221 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1222 it does, ensure that it does not exist in the
1223 configuration already
1226 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1229 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1230 """Add a node group to the configuration.
1233 logging.info("Adding node group %s to configuration", group.name)
1235 # Some code might need to add a node group with a pre-populated UUID
1236 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1237 # the "does this UUID" exist already check.
1239 self._EnsureUUID(group, ec_id)
1242 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1243 except errors.OpPrereqError:
1246 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1247 " node group (UUID: %s)" %
1248 (group.name, existing_uuid),
1249 errors.ECODE_EXISTS)
1252 group.ctime = group.mtime = time.time()
1253 group.UpgradeConfig()
1255 self._config_data.nodegroups[group.uuid] = group
1256 self._config_data.cluster.serial_no += 1
1258 @locking.ssynchronized(_config_lock)
1259 def RemoveNodeGroup(self, group_uuid):
1260 """Remove a node group from the configuration.
1262 @type group_uuid: string
1263 @param group_uuid: the UUID of the node group to remove
1266 logging.info("Removing node group %s from configuration", group_uuid)
1268 if group_uuid not in self._config_data.nodegroups:
1269 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1271 assert len(self._config_data.nodegroups) != 1, \
1272 "Group '%s' is the only group, cannot be removed" % group_uuid
1274 del self._config_data.nodegroups[group_uuid]
1275 self._config_data.cluster.serial_no += 1
1278 def _UnlockedLookupNodeGroup(self, target):
1279 """Lookup a node group's UUID.
1281 @type target: string or None
1282 @param target: group name or UUID or None to look for the default
1284 @return: nodegroup UUID
1285 @raises errors.OpPrereqError: when the target group cannot be found
1289 if len(self._config_data.nodegroups) != 1:
1290 raise errors.OpPrereqError("More than one node group exists. Target"
1291 " group must be specified explicitly.")
1293 return self._config_data.nodegroups.keys()[0]
1294 if target in self._config_data.nodegroups:
1296 for nodegroup in self._config_data.nodegroups.values():
1297 if nodegroup.name == target:
1298 return nodegroup.uuid
1299 raise errors.OpPrereqError("Node group '%s' not found" % target,
1302 @locking.ssynchronized(_config_lock, shared=1)
1303 def LookupNodeGroup(self, target):
1304 """Lookup a node group's UUID.
1306 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1308 @type target: string or None
1309 @param target: group name or UUID or None to look for the default
1311 @return: nodegroup UUID
1314 return self._UnlockedLookupNodeGroup(target)
1316 def _UnlockedGetNodeGroup(self, uuid):
1317 """Lookup a node group.
1320 @param uuid: group UUID
1321 @rtype: L{objects.NodeGroup} or None
1322 @return: nodegroup object, or None if not found
1325 if uuid not in self._config_data.nodegroups:
1328 return self._config_data.nodegroups[uuid]
1330 @locking.ssynchronized(_config_lock, shared=1)
1331 def GetNodeGroup(self, uuid):
1332 """Lookup a node group.
1335 @param uuid: group UUID
1336 @rtype: L{objects.NodeGroup} or None
1337 @return: nodegroup object, or None if not found
1340 return self._UnlockedGetNodeGroup(uuid)
1342 @locking.ssynchronized(_config_lock, shared=1)
1343 def GetAllNodeGroupsInfo(self):
1344 """Get the configuration of all node groups.
1347 return dict(self._config_data.nodegroups)
1349 @locking.ssynchronized(_config_lock, shared=1)
1350 def GetNodeGroupList(self):
1351 """Get a list of node groups.
1354 return self._config_data.nodegroups.keys()
1356 @locking.ssynchronized(_config_lock, shared=1)
1357 def GetNodeGroupMembersByNodes(self, nodes):
1358 """Get nodes which are member in the same nodegroups as the given nodes.
1361 ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1362 return frozenset(member_uuid
1363 for node_uuid in nodes
1365 self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1367 @locking.ssynchronized(_config_lock, shared=1)
1368 def GetMultiNodeGroupInfo(self, group_uuids):
1369 """Get the configuration of multiple node groups.
1371 @param group_uuids: List of node group UUIDs
1373 @return: List of tuples of (group_uuid, group_info)
1376 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1378 @locking.ssynchronized(_config_lock)
1379 def AddInstance(self, instance, ec_id):
1380 """Add an instance to the config.
1382 This should be used after creating a new instance.
1384 @type instance: L{objects.Instance}
1385 @param instance: the instance object
1388 if not isinstance(instance, objects.Instance):
1389 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1391 if instance.disk_template != constants.DT_DISKLESS:
1392 all_lvs = instance.MapLVsByNode()
1393 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1395 all_macs = self._AllMACs()
1396 for nic in instance.nics:
1397 if nic.mac in all_macs:
1398 raise errors.ConfigurationError("Cannot add instance %s:"
1399 " MAC address '%s' already in use." %
1400 (instance.name, nic.mac))
1402 self._CheckUniqueUUID(instance, include_temporary=False)
1404 instance.serial_no = 1
1405 instance.ctime = instance.mtime = time.time()
1406 self._config_data.instances[instance.uuid] = instance
1407 self._config_data.cluster.serial_no += 1
1408 self._UnlockedReleaseDRBDMinors(instance.uuid)
1409 self._UnlockedCommitTemporaryIps(ec_id)
1412 def _EnsureUUID(self, item, ec_id):
1413 """Ensures a given object has a valid UUID.
1415 @param item: the instance or node to be checked
1416 @param ec_id: the execution context id for the uuid reservation
1420 item.uuid = self._GenerateUniqueID(ec_id)
1422 self._CheckUniqueUUID(item, include_temporary=True)
1424 def _CheckUniqueUUID(self, item, include_temporary):
1425 """Checks that the UUID of the given object is unique.
1427 @param item: the instance or node to be checked
1428 @param include_temporary: whether temporarily generated UUID's should be
1429 included in the check. If the UUID of the item to be checked is
1430 a temporarily generated one, this has to be C{False}.
1434 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1435 if item.uuid in self._AllIDs(include_temporary=include_temporary):
1436 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1437 " in use" % (item.name, item.uuid))
1439 def _SetInstanceStatus(self, inst_uuid, status, disks_active):
1440 """Set the instance's status to a given value.
1443 if inst_uuid not in self._config_data.instances:
1444 raise errors.ConfigurationError("Unknown instance '%s'" %
1446 instance = self._config_data.instances[inst_uuid]
1449 status = instance.admin_state
1450 if disks_active is None:
1451 disks_active = instance.disks_active
1453 assert status in constants.ADMINST_ALL, \
1454 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1456 if instance.admin_state != status or \
1457 instance.disks_active != disks_active:
1458 instance.admin_state = status
1459 instance.disks_active = disks_active
1460 instance.serial_no += 1
1461 instance.mtime = time.time()
1464 @locking.ssynchronized(_config_lock)
1465 def MarkInstanceUp(self, inst_uuid):
1466 """Mark the instance status to up in the config.
1468 This also sets the instance disks active flag.
1471 self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True)
1473 @locking.ssynchronized(_config_lock)
1474 def MarkInstanceOffline(self, inst_uuid):
1475 """Mark the instance status to down in the config.
1477 This also clears the instance disks active flag.
1480 self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False)
1482 @locking.ssynchronized(_config_lock)
1483 def RemoveInstance(self, inst_uuid):
1484 """Remove the instance from the configuration.
1487 if inst_uuid not in self._config_data.instances:
1488 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1490 # If a network port has been allocated to the instance,
1491 # return it to the pool of free ports.
1492 inst = self._config_data.instances[inst_uuid]
1493 network_port = getattr(inst, "network_port", None)
1494 if network_port is not None:
1495 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1497 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1499 for nic in instance.nics:
1500 if nic.network and nic.ip:
1501 # Return all IP addresses to the respective address pools
1502 self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1504 del self._config_data.instances[inst_uuid]
1505 self._config_data.cluster.serial_no += 1
1508 @locking.ssynchronized(_config_lock)
1509 def RenameInstance(self, inst_uuid, new_name):
1510 """Rename an instance.
1512 This needs to be done in ConfigWriter and not by RemoveInstance
1513 combined with AddInstance as only we can guarantee an atomic
1517 if inst_uuid not in self._config_data.instances:
1518 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1520 inst = self._config_data.instances[inst_uuid]
1521 inst.name = new_name
1523 for (idx, disk) in enumerate(inst.disks):
1524 if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1525 # rename the file paths in logical and physical id
1526 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1527 disk.logical_id = (disk.logical_id[0],
1528 utils.PathJoin(file_storage_dir, inst.name,
1531 # Force update of ssconf files
1532 self._config_data.cluster.serial_no += 1
1536 @locking.ssynchronized(_config_lock)
1537 def MarkInstanceDown(self, inst_uuid):
1538 """Mark the status of an instance to down in the configuration.
1540 This does not touch the instance disks active flag, as shut down instances
1541 can still have active disks.
1544 self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None)
1546 @locking.ssynchronized(_config_lock)
1547 def MarkInstanceDisksActive(self, inst_uuid):
1548 """Mark the status of instance disks active.
1551 self._SetInstanceStatus(inst_uuid, None, True)
1553 @locking.ssynchronized(_config_lock)
1554 def MarkInstanceDisksInactive(self, inst_uuid):
1555 """Mark the status of instance disks inactive.
1558 self._SetInstanceStatus(inst_uuid, None, False)
1560 def _UnlockedGetInstanceList(self):
1561 """Get the list of instances.
1563 This function is for internal use, when the config lock is already held.
1566 return self._config_data.instances.keys()
1568 @locking.ssynchronized(_config_lock, shared=1)
1569 def GetInstanceList(self):
1570 """Get the list of instances.
1572 @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
1575 return self._UnlockedGetInstanceList()
1577 def ExpandInstanceName(self, short_name):
1578 """Attempt to expand an incomplete instance name.
1581 # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1582 all_insts = self.GetAllInstancesInfo().values()
1583 expanded_name = _MatchNameComponentIgnoreCase(
1584 short_name, [inst.name for inst in all_insts])
1586 if expanded_name is not None:
1587 # there has to be exactly one instance with that name
1588 inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
1589 return (inst.uuid, inst.name)
1593 def _UnlockedGetInstanceInfo(self, inst_uuid):
1594 """Returns information about an instance.
1596 This function is for internal use, when the config lock is already held.
1599 if inst_uuid not in self._config_data.instances:
1602 return self._config_data.instances[inst_uuid]
1604 @locking.ssynchronized(_config_lock, shared=1)
1605 def GetInstanceInfo(self, inst_uuid):
1606 """Returns information about an instance.
1608 It takes the information from the configuration file. Other information of
1609 an instance are taken from the live systems.
1611 @param inst_uuid: UUID of the instance
1613 @rtype: L{objects.Instance}
1614 @return: the instance object
1617 return self._UnlockedGetInstanceInfo(inst_uuid)
1619 @locking.ssynchronized(_config_lock, shared=1)
1620 def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1621 """Returns set of node group UUIDs for instance's nodes.
1626 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1628 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1631 nodes = [instance.primary_node]
1633 nodes = instance.all_nodes
1635 return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1636 for node_uuid in nodes)
1638 @locking.ssynchronized(_config_lock, shared=1)
1639 def GetInstanceNetworks(self, inst_uuid):
1640 """Returns set of network UUIDs for instance's nics.
1645 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1647 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1650 for nic in instance.nics:
1652 networks.add(nic.network)
1654 return frozenset(networks)
1656 @locking.ssynchronized(_config_lock, shared=1)
1657 def GetMultiInstanceInfo(self, inst_uuids):
1658 """Get the configuration of multiple instances.
1660 @param inst_uuids: list of instance UUIDs
1662 @return: list of tuples (instance UUID, instance_info), where
1663 instance_info is what would GetInstanceInfo return for the
1664 node, while keeping the original order
1667 return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1669 @locking.ssynchronized(_config_lock, shared=1)
1670 def GetMultiInstanceInfoByName(self, inst_names):
1671 """Get the configuration of multiple instances.
1673 @param inst_names: list of instance names
1675 @return: list of tuples (instance, instance_info), where
1676 instance_info is what would GetInstanceInfo return for the
1677 node, while keeping the original order
1681 for name in inst_names:
1682 instance = self._UnlockedGetInstanceInfoByName(name)
1683 result.append((instance.uuid, instance))
1686 @locking.ssynchronized(_config_lock, shared=1)
1687 def GetAllInstancesInfo(self):
1688 """Get the configuration of all instances.
1691 @return: dict of (instance, instance_info), where instance_info is what
1692 would GetInstanceInfo return for the node
1695 return self._UnlockedGetAllInstancesInfo()
1697 def _UnlockedGetAllInstancesInfo(self):
1698 my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1699 for inst_uuid in self._UnlockedGetInstanceList()])
1702 @locking.ssynchronized(_config_lock, shared=1)
1703 def GetInstancesInfoByFilter(self, filter_fn):
1704 """Get instance configuration with a filter.
1706 @type filter_fn: callable
1707 @param filter_fn: Filter function receiving instance object as parameter,
1708 returning boolean. Important: this function is called while the
1709 configuration locks is held. It must not do any complex work or call
1710 functions potentially leading to a deadlock. Ideally it doesn't call any
1711 other functions and just compares instance attributes.
1714 return dict((uuid, inst)
1715 for (uuid, inst) in self._config_data.instances.items()
1718 @locking.ssynchronized(_config_lock, shared=1)
1719 def GetInstanceInfoByName(self, inst_name):
1720 """Get the L{objects.Instance} object for a named instance.
1722 @param inst_name: name of the instance to get information for
1723 @type inst_name: string
1724 @return: the corresponding L{objects.Instance} instance or None if no
1725 information is available
1728 return self._UnlockedGetInstanceInfoByName(inst_name)
1730 def _UnlockedGetInstanceInfoByName(self, inst_name):
1731 for inst in self._UnlockedGetAllInstancesInfo().values():
1732 if inst.name == inst_name:
1736 def _UnlockedGetInstanceName(self, inst_uuid):
1737 inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1738 if inst_info is None:
1739 raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1740 return inst_info.name
1742 @locking.ssynchronized(_config_lock, shared=1)
1743 def GetInstanceName(self, inst_uuid):
1744 """Gets the instance name for the passed instance.
1746 @param inst_uuid: instance UUID to get name for
1747 @type inst_uuid: string
1749 @return: instance name
1752 return self._UnlockedGetInstanceName(inst_uuid)
1754 @locking.ssynchronized(_config_lock, shared=1)
1755 def GetInstanceNames(self, inst_uuids):
1756 """Gets the instance names for the passed list of nodes.
1758 @param inst_uuids: list of instance UUIDs to get names for
1759 @type inst_uuids: list of strings
1760 @rtype: list of strings
1761 @return: list of instance names
1764 return self._UnlockedGetInstanceNames(inst_uuids)
1766 def _UnlockedGetInstanceNames(self, inst_uuids):
1767 return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
1769 @locking.ssynchronized(_config_lock)
1770 def AddNode(self, node, ec_id):
1771 """Add a node to the configuration.
1773 @type node: L{objects.Node}
1774 @param node: a Node instance
1777 logging.info("Adding node %s to configuration", node.name)
1779 self._EnsureUUID(node, ec_id)
1782 node.ctime = node.mtime = time.time()
1783 self._UnlockedAddNodeToGroup(node.uuid, node.group)
1784 self._config_data.nodes[node.uuid] = node
1785 self._config_data.cluster.serial_no += 1
1788 @locking.ssynchronized(_config_lock)
1789 def RemoveNode(self, node_uuid):
1790 """Remove a node from the configuration.
1793 logging.info("Removing node %s from configuration", node_uuid)
1795 if node_uuid not in self._config_data.nodes:
1796 raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
1798 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_uuid])
1799 del self._config_data.nodes[node_uuid]
1800 self._config_data.cluster.serial_no += 1
1803 def ExpandNodeName(self, short_name):
1804 """Attempt to expand an incomplete node name into a node UUID.
1807 # Locking is done in L{ConfigWriter.GetAllNodesInfo}
1808 all_nodes = self.GetAllNodesInfo().values()
1809 expanded_name = _MatchNameComponentIgnoreCase(
1810 short_name, [node.name for node in all_nodes])
1812 if expanded_name is not None:
1813 # there has to be exactly one node with that name
1814 node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
1815 return (node.uuid, node.name)
1819 def _UnlockedGetNodeInfo(self, node_uuid):
1820 """Get the configuration of a node, as stored in the config.
1822 This function is for internal use, when the config lock is already
1825 @param node_uuid: the node UUID
1827 @rtype: L{objects.Node}
1828 @return: the node object
1831 if node_uuid not in self._config_data.nodes:
1834 return self._config_data.nodes[node_uuid]
1836 @locking.ssynchronized(_config_lock, shared=1)
1837 def GetNodeInfo(self, node_uuid):
1838 """Get the configuration of a node, as stored in the config.
1840 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1842 @param node_uuid: the node UUID
1844 @rtype: L{objects.Node}
1845 @return: the node object
1848 return self._UnlockedGetNodeInfo(node_uuid)
1850 @locking.ssynchronized(_config_lock, shared=1)
1851 def GetNodeInstances(self, node_uuid):
1852 """Get the instances of a node, as stored in the config.
1854 @param node_uuid: the node UUID
1856 @rtype: (list, list)
1857 @return: a tuple with two lists: the primary and the secondary instances
1862 for inst in self._config_data.instances.values():
1863 if inst.primary_node == node_uuid:
1864 pri.append(inst.uuid)
1865 if node_uuid in inst.secondary_nodes:
1866 sec.append(inst.uuid)
1869 @locking.ssynchronized(_config_lock, shared=1)
1870 def GetNodeGroupInstances(self, uuid, primary_only=False):
1871 """Get the instances of a node group.
1873 @param uuid: Node group UUID
1874 @param primary_only: Whether to only consider primary nodes
1876 @return: List of instance UUIDs in node group
1880 nodes_fn = lambda inst: [inst.primary_node]
1882 nodes_fn = lambda inst: inst.all_nodes
1884 return frozenset(inst.uuid
1885 for inst in self._config_data.instances.values()
1886 for node_uuid in nodes_fn(inst)
1887 if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
1889 def _UnlockedGetHvparamsString(self, hvname):
1890 """Return the string representation of the list of hyervisor parameters of
1891 the given hypervisor.
1893 @see: C{GetHvparams}
1897 hvparams = self._config_data.cluster.hvparams[hvname]
1898 for key in hvparams:
1899 result += "%s=%s\n" % (key, hvparams[key])
1902 @locking.ssynchronized(_config_lock, shared=1)
1903 def GetHvparamsString(self, hvname):
1904 """Return the hypervisor parameters of the given hypervisor.
1906 @type hvname: string
1907 @param hvname: name of a hypervisor
1909 @return: string containing key-value-pairs, one pair on each line;
1913 return self._UnlockedGetHvparamsString(hvname)
1915 def _UnlockedGetNodeList(self):
1916 """Return the list of nodes which are in the configuration.
1918 This function is for internal use, when the config lock is already
1924 return self._config_data.nodes.keys()
1926 @locking.ssynchronized(_config_lock, shared=1)
1927 def GetNodeList(self):
1928 """Return the list of nodes which are in the configuration.
1931 return self._UnlockedGetNodeList()
1933 def _UnlockedGetOnlineNodeList(self):
1934 """Return the list of nodes which are online.
1937 all_nodes = [self._UnlockedGetNodeInfo(node)
1938 for node in self._UnlockedGetNodeList()]
1939 return [node.uuid for node in all_nodes if not node.offline]
1941 @locking.ssynchronized(_config_lock, shared=1)
1942 def GetOnlineNodeList(self):
1943 """Return the list of nodes which are online.
1946 return self._UnlockedGetOnlineNodeList()
1948 @locking.ssynchronized(_config_lock, shared=1)
1949 def GetVmCapableNodeList(self):
1950 """Return the list of nodes which are not vm capable.
1953 all_nodes = [self._UnlockedGetNodeInfo(node)
1954 for node in self._UnlockedGetNodeList()]
1955 return [node.uuid for node in all_nodes if node.vm_capable]
1957 @locking.ssynchronized(_config_lock, shared=1)
1958 def GetNonVmCapableNodeList(self):
1959 """Return the list of nodes which are not vm capable.
1962 all_nodes = [self._UnlockedGetNodeInfo(node)
1963 for node in self._UnlockedGetNodeList()]
1964 return [node.uuid for node in all_nodes if not node.vm_capable]
1966 @locking.ssynchronized(_config_lock, shared=1)
1967 def GetMultiNodeInfo(self, node_uuids):
1968 """Get the configuration of multiple nodes.
1970 @param node_uuids: list of node UUIDs
1972 @return: list of tuples of (node, node_info), where node_info is
1973 what would GetNodeInfo return for the node, in the original
1977 return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
1979 def _UnlockedGetAllNodesInfo(self):
1980 """Gets configuration of all nodes.
1982 @note: See L{GetAllNodesInfo}
1985 return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
1986 for node_uuid in self._UnlockedGetNodeList()])
1988 @locking.ssynchronized(_config_lock, shared=1)
1989 def GetAllNodesInfo(self):
1990 """Get the configuration of all nodes.
1993 @return: dict of (node, node_info), where node_info is what
1994 would GetNodeInfo return for the node
1997 return self._UnlockedGetAllNodesInfo()
1999 def _UnlockedGetNodeInfoByName(self, node_name):
2000 for node in self._UnlockedGetAllNodesInfo().values():
2001 if node.name == node_name:
2005 @locking.ssynchronized(_config_lock, shared=1)
2006 def GetNodeInfoByName(self, node_name):
2007 """Get the L{objects.Node} object for a named node.
2009 @param node_name: name of the node to get information for
2010 @type node_name: string
2011 @return: the corresponding L{objects.Node} instance or None if no
2012 information is available
2015 return self._UnlockedGetNodeInfoByName(node_name)
2017 def _UnlockedGetNodeName(self, node_spec):
2018 if isinstance(node_spec, objects.Node):
2019 return node_spec.name
2020 elif isinstance(node_spec, basestring):
2021 node_info = self._UnlockedGetNodeInfo(node_spec)
2022 if node_info is None:
2023 raise errors.OpExecError("Unknown node: %s" % node_spec)
2024 return node_info.name
2026 raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2028 @locking.ssynchronized(_config_lock, shared=1)
2029 def GetNodeName(self, node_spec):
2030 """Gets the node name for the passed node.
2032 @param node_spec: node to get names for
2033 @type node_spec: either node UUID or a L{objects.Node} object
2038 return self._UnlockedGetNodeName(node_spec)
2040 def _UnlockedGetNodeNames(self, node_specs):
2041 return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2043 @locking.ssynchronized(_config_lock, shared=1)
2044 def GetNodeNames(self, node_specs):
2045 """Gets the node names for the passed list of nodes.
2047 @param node_specs: list of nodes to get names for
2048 @type node_specs: list of either node UUIDs or L{objects.Node} objects
2049 @rtype: list of strings
2050 @return: list of node names
2053 return self._UnlockedGetNodeNames(node_specs)
2055 @locking.ssynchronized(_config_lock, shared=1)
2056 def GetNodeGroupsFromNodes(self, node_uuids):
2057 """Returns groups for a list of nodes.
2059 @type node_uuids: list of string
2060 @param node_uuids: List of node UUIDs
2064 return frozenset(self._UnlockedGetNodeInfo(uuid).group
2065 for uuid in node_uuids)
2067 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2068 """Get the number of current and maximum desired and possible candidates.
2070 @type exceptions: list
2071 @param exceptions: if passed, list of nodes that should be ignored
2073 @return: tuple of (current, desired and possible, possible)
2076 mc_now = mc_should = mc_max = 0
2077 for node in self._config_data.nodes.values():
2078 if exceptions and node.uuid in exceptions:
2080 if not (node.offline or node.drained) and node.master_capable:
2082 if node.master_candidate:
2084 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
2085 return (mc_now, mc_should, mc_max)
2087 @locking.ssynchronized(_config_lock, shared=1)
2088 def GetMasterCandidateStats(self, exceptions=None):
2089 """Get the number of current and maximum possible candidates.
2091 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2093 @type exceptions: list
2094 @param exceptions: if passed, list of nodes that should be ignored
2096 @return: tuple of (current, max)
2099 return self._UnlockedGetMasterCandidateStats(exceptions)
2101 @locking.ssynchronized(_config_lock)
2102 def MaintainCandidatePool(self, exception_node_uuids):
2103 """Try to grow the candidate pool to the desired size.
2105 @type exception_node_uuids: list
2106 @param exception_node_uuids: if passed, list of nodes that should be ignored
2108 @return: list with the adjusted nodes (L{objects.Node} instances)
2111 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2112 exception_node_uuids)
2115 node_list = self._config_data.nodes.keys()
2116 random.shuffle(node_list)
2117 for uuid in node_list:
2118 if mc_now >= mc_max:
2120 node = self._config_data.nodes[uuid]
2121 if (node.master_candidate or node.offline or node.drained or
2122 node.uuid in exception_node_uuids or not node.master_capable):
2124 mod_list.append(node)
2125 node.master_candidate = True
2128 if mc_now != mc_max:
2129 # this should not happen
2130 logging.warning("Warning: MaintainCandidatePool didn't manage to"
2131 " fill the candidate pool (%d/%d)", mc_now, mc_max)
2133 self._config_data.cluster.serial_no += 1
2138 def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2139 """Add a given node to the specified group.
2142 if nodegroup_uuid not in self._config_data.nodegroups:
2143 # This can happen if a node group gets deleted between its lookup and
2144 # when we're adding the first node to it, since we don't keep a lock in
2145 # the meantime. It's ok though, as we'll fail cleanly if the node group
2146 # is not found anymore.
2147 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2148 if node_uuid not in self._config_data.nodegroups[nodegroup_uuid].members:
2149 self._config_data.nodegroups[nodegroup_uuid].members.append(node_uuid)
2151 def _UnlockedRemoveNodeFromGroup(self, node):
2152 """Remove a given node from its group.
2155 nodegroup = node.group
2156 if nodegroup not in self._config_data.nodegroups:
2157 logging.warning("Warning: node '%s' has unknown node group '%s'"
2158 " (while being removed from it)", node.uuid, nodegroup)
2159 nodegroup_obj = self._config_data.nodegroups[nodegroup]
2160 if node.uuid not in nodegroup_obj.members:
2161 logging.warning("Warning: node '%s' not a member of its node group '%s'"
2162 " (while being removed from it)", node.uuid, nodegroup)
2164 nodegroup_obj.members.remove(node.uuid)
2166 @locking.ssynchronized(_config_lock)
2167 def AssignGroupNodes(self, mods):
2168 """Changes the group of a number of nodes.
2170 @type mods: list of tuples; (node name, new group UUID)
2171 @param mods: Node membership modifications
2174 groups = self._config_data.nodegroups
2175 nodes = self._config_data.nodes
2179 # Try to resolve UUIDs first
2180 for (node_uuid, new_group_uuid) in mods:
2182 node = nodes[node_uuid]
2184 raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2186 if node.group == new_group_uuid:
2187 # Node is being assigned to its current group
2188 logging.debug("Node '%s' was assigned to its current group (%s)",
2189 node_uuid, node.group)
2192 # Try to find current group of node
2194 old_group = groups[node.group]
2196 raise errors.ConfigurationError("Unable to find old group '%s'" %
2199 # Try to find new group for node
2201 new_group = groups[new_group_uuid]
2203 raise errors.ConfigurationError("Unable to find new group '%s'" %
2206 assert node.uuid in old_group.members, \
2207 ("Inconsistent configuration: node '%s' not listed in members for its"
2208 " old group '%s'" % (node.uuid, old_group.uuid))
2209 assert node.uuid not in new_group.members, \
2210 ("Inconsistent configuration: node '%s' already listed in members for"
2211 " its new group '%s'" % (node.uuid, new_group.uuid))
2213 resmod.append((node, old_group, new_group))
2216 for (node, old_group, new_group) in resmod:
2217 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2218 "Assigning to current group is not possible"
2220 node.group = new_group.uuid
2222 # Update members of involved groups
2223 if node.uuid in old_group.members:
2224 old_group.members.remove(node.uuid)
2225 if node.uuid not in new_group.members:
2226 new_group.members.append(node.uuid)
2228 # Update timestamps and serials (only once per node/group object)
2230 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2234 # Force ssconf update
2235 self._config_data.cluster.serial_no += 1
2239 def _BumpSerialNo(self):
2240 """Bump up the serial number of the config.
2243 self._config_data.serial_no += 1
2244 self._config_data.mtime = time.time()
2246 def _AllUUIDObjects(self):
2247 """Returns all objects with uuid attributes.
2250 return (self._config_data.instances.values() +
2251 self._config_data.nodes.values() +
2252 self._config_data.nodegroups.values() +
2253 self._config_data.networks.values() +
2256 [self._config_data.cluster])
2258 def _OpenConfig(self, accept_foreign):
2259 """Read the config data from disk.
2262 raw_data = utils.ReadFile(self._cfg_file)
2265 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2266 except Exception, err:
2267 raise errors.ConfigurationError(err)
2269 # Make sure the configuration has the right version
2270 _ValidateConfig(data)
2272 if (not hasattr(data, "cluster") or
2273 not hasattr(data.cluster, "rsahostkeypub")):
2274 raise errors.ConfigurationError("Incomplete configuration"
2275 " (missing cluster.rsahostkeypub)")
2277 if not data.cluster.master_node in data.nodes:
2278 msg = ("The configuration denotes node %s as master, but does not"
2279 " contain information about this node" %
2280 data.cluster.master_node)
2281 raise errors.ConfigurationError(msg)
2283 master_info = data.nodes[data.cluster.master_node]
2284 if master_info.name != self._my_hostname and not accept_foreign:
2285 msg = ("The configuration denotes node %s as master, while my"
2286 " hostname is %s; opening a foreign configuration is only"
2287 " possible in accept_foreign mode" %
2288 (master_info.name, self._my_hostname))
2289 raise errors.ConfigurationError(msg)
2291 self._config_data = data
2292 # reset the last serial as -1 so that the next write will cause
2294 self._last_cluster_serial = -1
2296 # Upgrade configuration if needed
2297 self._UpgradeConfig()
2299 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2301 def _UpgradeConfig(self):
2302 """Run any upgrade steps.
2304 This method performs both in-object upgrades and also update some data
2305 elements that need uniqueness across the whole configuration or interact
2308 @warning: this function will call L{_WriteConfig()}, but also
2309 L{DropECReservations} so it needs to be called only from a
2310 "safe" place (the constructor). If one wanted to call it with
2311 the lock held, a DropECReservationUnlocked would need to be
2312 created first, to avoid causing deadlock.
2315 # Keep a copy of the persistent part of _config_data to check for changes
2316 # Serialization doesn't guarantee order in dictionaries
2317 oldconf = copy.deepcopy(self._config_data.ToDict())
2319 # In-object upgrades
2320 self._config_data.UpgradeConfig()
2322 for item in self._AllUUIDObjects():
2323 if item.uuid is None:
2324 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2325 if not self._config_data.nodegroups:
2326 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2327 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2329 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2330 for node in self._config_data.nodes.values():
2332 node.group = self.LookupNodeGroup(None)
2333 # This is technically *not* an upgrade, but needs to be done both when
2334 # nodegroups are being added, and upon normally loading the config,
2335 # because the members list of a node group is discarded upon
2336 # serializing/deserializing the object.
2337 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2339 modified = (oldconf != self._config_data.ToDict())
2342 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2343 # only called at config init time, without the lock held
2344 self.DropECReservations(_UPGRADE_CONFIG_JID)
2346 config_errors = self._UnlockedVerifyConfig()
2348 errmsg = ("Loaded configuration data is not consistent: %s" %
2349 (utils.CommaJoin(config_errors)))
2350 logging.critical(errmsg)
2352 def _DistributeConfig(self, feedback_fn):
2353 """Distribute the configuration to the other nodes.
2355 Currently, this only copies the configuration file. In the future,
2356 it could be used to encapsulate the 2/3-phase update mechanism.
2366 myhostname = self._my_hostname
2367 # we can skip checking whether _UnlockedGetNodeInfo returns None
2368 # since the node list comes from _UnlocketGetNodeList, and we are
2369 # called with the lock held, so no modifications should take place
2371 for node_uuid in self._UnlockedGetNodeList():
2372 node_info = self._UnlockedGetNodeInfo(node_uuid)
2373 if node_info.name == myhostname or not node_info.master_candidate:
2375 node_list.append(node_info.name)
2376 addr_list.append(node_info.primary_ip)
2378 # TODO: Use dedicated resolver talking to config writer for name resolution
2380 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2381 for to_node, to_result in result.items():
2382 msg = to_result.fail_msg
2384 msg = ("Copy of file %s to node %s failed: %s" %
2385 (self._cfg_file, to_node, msg))
2395 def _WriteConfig(self, destination=None, feedback_fn=None):
2396 """Write the configuration data to persistent storage.
2399 assert feedback_fn is None or callable(feedback_fn)
2401 # Warn on config errors, but don't abort the save - the
2402 # configuration has already been modified, and we can't revert;
2403 # the best we can do is to warn the user and save as is, leaving
2404 # recovery to the user
2405 config_errors = self._UnlockedVerifyConfig()
2407 errmsg = ("Configuration data is not consistent: %s" %
2408 (utils.CommaJoin(config_errors)))
2409 logging.critical(errmsg)
2413 if destination is None:
2414 destination = self._cfg_file
2415 self._BumpSerialNo()
2416 txt = serializer.Dump(self._config_data.ToDict())
2418 getents = self._getents()
2420 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2421 close=False, gid=getents.confd_gid, mode=0640)
2422 except errors.LockError:
2423 raise errors.ConfigurationError("The configuration file has been"
2424 " modified since the last write, cannot"
2427 self._cfg_id = utils.GetFileID(fd=fd)
2431 self.write_count += 1
2433 # and redistribute the config file to master candidates
2434 self._DistributeConfig(feedback_fn)
2436 # Write ssconf files on all nodes (including locally)
2437 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2438 if not self._offline:
2439 result = self._GetRpc(None).call_write_ssconf_files(
2440 self._UnlockedGetNodeNames(self._UnlockedGetOnlineNodeList()),
2441 self._UnlockedGetSsconfValues())
2443 for nname, nresu in result.items():
2444 msg = nresu.fail_msg
2446 errmsg = ("Error while uploading ssconf files to"
2447 " node %s: %s" % (nname, msg))
2448 logging.warning(errmsg)
2453 self._last_cluster_serial = self._config_data.cluster.serial_no
2455 def _GetAllHvparamsStrings(self, hypervisors):
2456 """Get the hvparams of all given hypervisors from the config.
2458 @type hypervisors: list of string
2459 @param hypervisors: list of hypervisor names
2460 @rtype: dict of strings
2461 @returns: dictionary mapping the hypervisor name to a string representation
2462 of the hypervisor's hvparams
2466 for hv in hypervisors:
2467 hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2471 def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2472 """Extends the ssconf_values dictionary by hvparams.
2474 @type ssconf_values: dict of strings
2475 @param ssconf_values: dictionary mapping ssconf_keys to strings
2476 representing the content of ssconf files
2477 @type all_hvparams: dict of strings
2478 @param all_hvparams: dictionary mapping hypervisor names to a string
2479 representation of their hvparams
2480 @rtype: same as ssconf_values
2481 @returns: the ssconf_values dictionary extended by hvparams
2484 for hv in all_hvparams:
2485 ssconf_key = constants.SS_HVPARAMS_PREF + hv
2486 ssconf_values[ssconf_key] = all_hvparams[hv]
2487 return ssconf_values
2489 def _UnlockedGetSsconfValues(self):
2490 """Return the values needed by ssconf.
2493 @return: a dictionary with keys the ssconf names and values their
2498 instance_names = utils.NiceSort(
2499 [inst.name for inst in
2500 self._UnlockedGetAllInstancesInfo().values()])
2501 node_infos = self._UnlockedGetAllNodesInfo().values()
2502 node_names = [node.name for node in node_infos]
2503 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2504 for ninfo in node_infos]
2505 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2506 for ninfo in node_infos]
2508 instance_data = fn(instance_names)
2509 off_data = fn(node.name for node in node_infos if node.offline)
2510 on_data = fn(node.name for node in node_infos if not node.offline)
2511 mc_data = fn(node.name for node in node_infos if node.master_candidate)
2512 mc_ips_data = fn(node.primary_ip for node in node_infos
2513 if node.master_candidate)
2514 node_data = fn(node_names)
2515 node_pri_ips_data = fn(node_pri_ips)
2516 node_snd_ips_data = fn(node_snd_ips)
2518 cluster = self._config_data.cluster
2519 cluster_tags = fn(cluster.GetTags())
2521 hypervisor_list = fn(cluster.enabled_hypervisors)
2522 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2524 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2526 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2527 self._config_data.nodegroups.values()]
2528 nodegroups_data = fn(utils.NiceSort(nodegroups))
2529 networks = ["%s %s" % (net.uuid, net.name) for net in
2530 self._config_data.networks.values()]
2531 networks_data = fn(utils.NiceSort(networks))
2534 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2535 constants.SS_CLUSTER_TAGS: cluster_tags,
2536 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2537 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2538 constants.SS_MASTER_CANDIDATES: mc_data,
2539 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2540 constants.SS_MASTER_IP: cluster.master_ip,
2541 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2542 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2543 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2544 constants.SS_NODE_LIST: node_data,
2545 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2546 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2547 constants.SS_OFFLINE_NODES: off_data,
2548 constants.SS_ONLINE_NODES: on_data,
2549 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2550 constants.SS_INSTANCE_LIST: instance_data,
2551 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2552 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2553 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2554 constants.SS_UID_POOL: uid_pool,
2555 constants.SS_NODEGROUPS: nodegroups_data,
2556 constants.SS_NETWORKS: networks_data,
2558 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2560 bad_values = [(k, v) for k, v in ssconf_values.items()
2561 if not isinstance(v, (str, basestring))]
2563 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2564 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2565 " values: %s" % err)
2566 return ssconf_values
2568 @locking.ssynchronized(_config_lock, shared=1)
2569 def GetSsconfValues(self):
2570 """Wrapper using lock around _UnlockedGetSsconf().
2573 return self._UnlockedGetSsconfValues()
2575 @locking.ssynchronized(_config_lock, shared=1)
2576 def GetVGName(self):
2577 """Return the volume group name.
2580 return self._config_data.cluster.volume_group_name
2582 @locking.ssynchronized(_config_lock)
2583 def SetVGName(self, vg_name):
2584 """Set the volume group name.
2587 self._config_data.cluster.volume_group_name = vg_name
2588 self._config_data.cluster.serial_no += 1
2591 @locking.ssynchronized(_config_lock, shared=1)
2592 def GetDRBDHelper(self):
2593 """Return DRBD usermode helper.
2596 return self._config_data.cluster.drbd_usermode_helper
2598 @locking.ssynchronized(_config_lock)
2599 def SetDRBDHelper(self, drbd_helper):
2600 """Set DRBD usermode helper.
2603 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2604 self._config_data.cluster.serial_no += 1
2607 @locking.ssynchronized(_config_lock, shared=1)
2608 def GetMACPrefix(self):
2609 """Return the mac prefix.
2612 return self._config_data.cluster.mac_prefix
2614 @locking.ssynchronized(_config_lock, shared=1)
2615 def GetClusterInfo(self):
2616 """Returns information about the cluster
2618 @rtype: L{objects.Cluster}
2619 @return: the cluster object
2622 return self._config_data.cluster
2624 @locking.ssynchronized(_config_lock, shared=1)
2625 def HasAnyDiskOfType(self, dev_type):
2626 """Check if in there is at disk of the given type in the configuration.
2629 return self._config_data.HasAnyDiskOfType(dev_type)
2631 @locking.ssynchronized(_config_lock)
2632 def Update(self, target, feedback_fn, ec_id=None):
2633 """Notify function to be called after updates.
2635 This function must be called when an object (as returned by
2636 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2637 caller wants the modifications saved to the backing store. Note
2638 that all modified objects will be saved, but the target argument
2639 is the one the caller wants to ensure that it's saved.
2641 @param target: an instance of either L{objects.Cluster},
2642 L{objects.Node} or L{objects.Instance} which is existing in
2644 @param feedback_fn: Callable feedback function
2647 if self._config_data is None:
2648 raise errors.ProgrammerError("Configuration file not read,"
2650 update_serial = False
2651 if isinstance(target, objects.Cluster):
2652 test = target == self._config_data.cluster
2653 elif isinstance(target, objects.Node):
2654 test = target in self._config_data.nodes.values()
2655 update_serial = True
2656 elif isinstance(target, objects.Instance):
2657 test = target in self._config_data.instances.values()
2658 elif isinstance(target, objects.NodeGroup):
2659 test = target in self._config_data.nodegroups.values()
2660 elif isinstance(target, objects.Network):
2661 test = target in self._config_data.networks.values()
2663 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2664 " ConfigWriter.Update" % type(target))
2666 raise errors.ConfigurationError("Configuration updated since object"
2667 " has been read or unknown object")
2668 target.serial_no += 1
2669 target.mtime = now = time.time()
2672 # for node updates, we need to increase the cluster serial too
2673 self._config_data.cluster.serial_no += 1
2674 self._config_data.cluster.mtime = now
2676 if isinstance(target, objects.Instance):
2677 self._UnlockedReleaseDRBDMinors(target.uuid)
2679 if ec_id is not None:
2680 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2681 self._UnlockedCommitTemporaryIps(ec_id)
2683 self._WriteConfig(feedback_fn=feedback_fn)
2685 @locking.ssynchronized(_config_lock)
2686 def DropECReservations(self, ec_id):
2687 """Drop per-execution-context reservations
2690 for rm in self._all_rms:
2691 rm.DropECReservations(ec_id)
2693 @locking.ssynchronized(_config_lock, shared=1)
2694 def GetAllNetworksInfo(self):
2695 """Get configuration info of all the networks.
2698 return dict(self._config_data.networks)
2700 def _UnlockedGetNetworkList(self):
2701 """Get the list of networks.
2703 This function is for internal use, when the config lock is already held.
2706 return self._config_data.networks.keys()
2708 @locking.ssynchronized(_config_lock, shared=1)
2709 def GetNetworkList(self):
2710 """Get the list of networks.
2712 @return: array of networks, ex. ["main", "vlan100", "200]
2715 return self._UnlockedGetNetworkList()
2717 @locking.ssynchronized(_config_lock, shared=1)
2718 def GetNetworkNames(self):
2719 """Get a list of network names
2723 for net in self._config_data.networks.values()]
2726 def _UnlockedGetNetwork(self, uuid):
2727 """Returns information about a network.
2729 This function is for internal use, when the config lock is already held.
2732 if uuid not in self._config_data.networks:
2735 return self._config_data.networks[uuid]
2737 @locking.ssynchronized(_config_lock, shared=1)
2738 def GetNetwork(self, uuid):
2739 """Returns information about a network.
2741 It takes the information from the configuration file.
2743 @param uuid: UUID of the network
2745 @rtype: L{objects.Network}
2746 @return: the network object
2749 return self._UnlockedGetNetwork(uuid)
2751 @locking.ssynchronized(_config_lock)
2752 def AddNetwork(self, net, ec_id, check_uuid=True):
2753 """Add a network to the configuration.
2755 @type net: L{objects.Network}
2756 @param net: the Network object to add
2758 @param ec_id: unique id for the job to use when creating a missing UUID
2761 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2764 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2765 """Add a network to the configuration.
2768 logging.info("Adding network %s to configuration", net.name)
2771 self._EnsureUUID(net, ec_id)
2774 net.ctime = net.mtime = time.time()
2775 self._config_data.networks[net.uuid] = net
2776 self._config_data.cluster.serial_no += 1
2778 def _UnlockedLookupNetwork(self, target):
2779 """Lookup a network's UUID.
2781 @type target: string
2782 @param target: network name or UUID
2784 @return: network UUID
2785 @raises errors.OpPrereqError: when the target network cannot be found
2790 if target in self._config_data.networks:
2792 for net in self._config_data.networks.values():
2793 if net.name == target:
2795 raise errors.OpPrereqError("Network '%s' not found" % target,
2798 @locking.ssynchronized(_config_lock, shared=1)
2799 def LookupNetwork(self, target):
2800 """Lookup a network's UUID.
2802 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2804 @type target: string
2805 @param target: network name or UUID
2807 @return: network UUID
2810 return self._UnlockedLookupNetwork(target)
2812 @locking.ssynchronized(_config_lock)
2813 def RemoveNetwork(self, network_uuid):
2814 """Remove a network from the configuration.
2816 @type network_uuid: string
2817 @param network_uuid: the UUID of the network to remove
2820 logging.info("Removing network %s from configuration", network_uuid)
2822 if network_uuid not in self._config_data.networks:
2823 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2825 del self._config_data.networks[network_uuid]
2826 self._config_data.cluster.serial_no += 1
2829 def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
2830 """Get the netparams (mode, link) of a network.
2832 Get a network's netparams for a given node.
2834 @type net_uuid: string
2835 @param net_uuid: network uuid
2836 @type node_uuid: string
2837 @param node_uuid: node UUID
2838 @rtype: dict or None
2842 node_info = self._UnlockedGetNodeInfo(node_uuid)
2843 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2844 netparams = nodegroup_info.networks.get(net_uuid, None)
2848 @locking.ssynchronized(_config_lock, shared=1)
2849 def GetGroupNetParams(self, net_uuid, node_uuid):
2850 """Locking wrapper of _UnlockedGetGroupNetParams()
2853 return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
2855 @locking.ssynchronized(_config_lock, shared=1)
2856 def CheckIPInNodeGroup(self, ip, node_uuid):
2857 """Check IP uniqueness in nodegroup.
2859 Check networks that are connected in the node's node group
2860 if ip is contained in any of them. Used when creating/adding
2861 a NIC to ensure uniqueness among nodegroups.
2864 @param ip: ip address
2865 @type node_uuid: string
2866 @param node_uuid: node UUID
2867 @rtype: (string, dict) or (None, None)
2868 @return: (network name, netparams)
2873 node_info = self._UnlockedGetNodeInfo(node_uuid)
2874 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2875 for net_uuid in nodegroup_info.networks.keys():
2876 net_info = self._UnlockedGetNetwork(net_uuid)
2877 pool = network.AddressPool(net_info)
2878 if pool.Contains(ip):
2879 return (net_info.name, nodegroup_info.networks[net_uuid])