4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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)
448 def _AllIDs(self, include_temporary):
449 """Compute the list of all UUIDs and names we have.
451 @type include_temporary: boolean
452 @param include_temporary: whether to include the _temporary_ids set
454 @return: a set of IDs
458 if include_temporary:
459 existing.update(self._temporary_ids.GetReserved())
460 existing.update(self._AllLVs())
461 existing.update(self._config_data.instances.keys())
462 existing.update(self._config_data.nodes.keys())
463 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
466 def _GenerateUniqueID(self, ec_id):
467 """Generate an unique UUID.
469 This checks the current node, instances and disk names for
473 @return: the unique id
476 existing = self._AllIDs(include_temporary=False)
477 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
479 @locking.ssynchronized(_config_lock, shared=1)
480 def GenerateUniqueID(self, ec_id):
481 """Generate an unique ID.
483 This is just a wrapper over the unlocked version.
486 @param ec_id: unique id for the job to reserve the id to
489 return self._GenerateUniqueID(ec_id)
492 """Return all MACs present in the config.
495 @return: the list of all MACs
499 for instance in self._config_data.instances.values():
500 for nic in instance.nics:
501 result.append(nic.mac)
505 def _AllDRBDSecrets(self):
506 """Return all DRBD secrets present in the config.
509 @return: the list of all DRBD secrets
512 def helper(disk, result):
513 """Recursively gather secrets from this disk."""
514 if disk.dev_type == constants.DT_DRBD8:
515 result.append(disk.logical_id[5])
517 for child in disk.children:
518 helper(child, result)
521 for instance in self._config_data.instances.values():
522 for disk in instance.disks:
527 def _CheckDiskIDs(self, disk, l_ids, p_ids):
528 """Compute duplicate disk IDs
530 @type disk: L{objects.Disk}
531 @param disk: the disk at which to start searching
533 @param l_ids: list of current logical ids
535 @param p_ids: list of current physical ids
537 @return: a list of error messages
541 if disk.logical_id is not None:
542 if disk.logical_id in l_ids:
543 result.append("duplicate logical id %s" % str(disk.logical_id))
545 l_ids.append(disk.logical_id)
546 if disk.physical_id is not None:
547 if disk.physical_id in p_ids:
548 result.append("duplicate physical id %s" % str(disk.physical_id))
550 p_ids.append(disk.physical_id)
553 for child in disk.children:
554 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
557 def _UnlockedVerifyConfig(self):
561 @return: a list of error messages; a non-empty list signifies
565 # pylint: disable=R0914
569 data = self._config_data
570 cluster = data.cluster
574 # global cluster checks
575 if not cluster.enabled_hypervisors:
576 result.append("enabled hypervisors list doesn't have any entries")
577 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
579 result.append("enabled hypervisors contains invalid entries: %s" %
581 missing_hvp = (set(cluster.enabled_hypervisors) -
582 set(cluster.hvparams.keys()))
584 result.append("hypervisor parameters missing for the enabled"
585 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
587 if cluster.master_node not in data.nodes:
588 result.append("cluster has invalid primary node '%s'" %
591 def _helper(owner, attr, value, template):
593 utils.ForceDictType(value, template)
594 except errors.GenericError, err:
595 result.append("%s has invalid %s: %s" % (owner, attr, err))
597 def _helper_nic(owner, params):
599 objects.NIC.CheckParameterSyntax(params)
600 except errors.ConfigurationError, err:
601 result.append("%s has invalid nicparams: %s" % (owner, err))
603 def _helper_ipolicy(owner, params, check_std):
605 objects.InstancePolicy.CheckParameterSyntax(params, check_std)
606 except errors.ConfigurationError, err:
607 result.append("%s has invalid instance policy: %s" % (owner, err))
609 def _helper_ispecs(owner, params):
610 for key, value in params.items():
611 if key in constants.IPOLICY_ISPECS:
612 fullkey = "ipolicy/" + key
613 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
615 # FIXME: assuming list type
616 if key in constants.IPOLICY_PARAMETERS:
620 if not isinstance(value, exp_type):
621 result.append("%s has invalid instance policy: for %s,"
622 " expecting %s, got %s" %
623 (owner, key, exp_type.__name__, type(value)))
625 # check cluster parameters
626 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
627 constants.BES_PARAMETER_TYPES)
628 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
629 constants.NICS_PARAMETER_TYPES)
630 _helper_nic("cluster", cluster.SimpleFillNIC({}))
631 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
632 constants.NDS_PARAMETER_TYPES)
633 _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
634 _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
636 # per-instance checks
637 for instance_name in data.instances:
638 instance = data.instances[instance_name]
639 if instance.name != instance_name:
640 result.append("instance '%s' is indexed by wrong name '%s'" %
641 (instance.name, instance_name))
642 if instance.primary_node not in data.nodes:
643 result.append("instance '%s' has invalid primary node '%s'" %
644 (instance_name, instance.primary_node))
645 for snode in instance.secondary_nodes:
646 if snode not in data.nodes:
647 result.append("instance '%s' has invalid secondary node '%s'" %
648 (instance_name, snode))
649 for idx, nic in enumerate(instance.nics):
650 if nic.mac in seen_macs:
651 result.append("instance '%s' has NIC %d mac %s duplicate" %
652 (instance_name, idx, nic.mac))
654 seen_macs.append(nic.mac)
656 filled = cluster.SimpleFillNIC(nic.nicparams)
657 owner = "instance %s nic %d" % (instance.name, idx)
658 _helper(owner, "nicparams",
659 filled, constants.NICS_PARAMETER_TYPES)
660 _helper_nic(owner, filled)
663 if instance.beparams:
664 _helper("instance %s" % instance.name, "beparams",
665 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
667 # gather the drbd ports for duplicate checks
668 for (idx, dsk) in enumerate(instance.disks):
669 if dsk.dev_type in constants.LDS_DRBD:
670 tcp_port = dsk.logical_id[2]
671 if tcp_port not in ports:
673 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
674 # gather network port reservation
675 net_port = getattr(instance, "network_port", None)
676 if net_port is not None:
677 if net_port not in ports:
679 ports[net_port].append((instance.name, "network port"))
681 # instance disk verify
682 for idx, disk in enumerate(instance.disks):
683 result.extend(["instance '%s' disk %d error: %s" %
684 (instance.name, idx, msg) for msg in disk.Verify()])
685 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
687 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
689 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
690 (idx, exp_name, actual_name))
691 for (idx, exp_name, actual_name) in wrong_names)
693 result.append("Instance '%s' has wrongly named disks: %s" %
694 (instance.name, tmp))
696 # cluster-wide pool of free ports
697 for free_port in cluster.tcpudp_port_pool:
698 if free_port not in ports:
699 ports[free_port] = []
700 ports[free_port].append(("cluster", "port marked as free"))
702 # compute tcp/udp duplicate ports
708 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
709 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
711 # highest used tcp port check
713 if keys[-1] > cluster.highest_used_port:
714 result.append("Highest used port mismatch, saved %s, computed %s" %
715 (cluster.highest_used_port, keys[-1]))
717 if not data.nodes[cluster.master_node].master_candidate:
718 result.append("Master node is not a master candidate")
720 # master candidate checks
721 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
723 result.append("Not enough master candidates: actual %d, target %d" %
727 for node_name, node in data.nodes.items():
728 if node.name != node_name:
729 result.append("Node '%s' is indexed by wrong name '%s'" %
730 (node.name, node_name))
731 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
732 result.append("Node %s state is invalid: master_candidate=%s,"
733 " drain=%s, offline=%s" %
734 (node.name, node.master_candidate, node.drained,
736 if node.group not in data.nodegroups:
737 result.append("Node '%s' has invalid group '%s'" %
738 (node.name, node.group))
740 _helper("node %s" % node.name, "ndparams",
741 cluster.FillND(node, data.nodegroups[node.group]),
742 constants.NDS_PARAMETER_TYPES)
743 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
745 result.append("Node '%s' has some global parameters set: %s" %
746 (node.name, utils.CommaJoin(used_globals)))
749 nodegroups_names = set()
750 for nodegroup_uuid in data.nodegroups:
751 nodegroup = data.nodegroups[nodegroup_uuid]
752 if nodegroup.uuid != nodegroup_uuid:
753 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
754 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
755 if utils.UUID_RE.match(nodegroup.name.lower()):
756 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
757 (nodegroup.name, nodegroup.uuid))
758 if nodegroup.name in nodegroups_names:
759 result.append("duplicate node group name '%s'" % nodegroup.name)
761 nodegroups_names.add(nodegroup.name)
762 group_name = "group %s" % nodegroup.name
763 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
765 _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
766 if nodegroup.ndparams:
767 _helper(group_name, "ndparams",
768 cluster.SimpleFillND(nodegroup.ndparams),
769 constants.NDS_PARAMETER_TYPES)
772 _, duplicates = self._UnlockedComputeDRBDMap()
773 for node, minor, instance_a, instance_b in duplicates:
774 result.append("DRBD minor %d on node %s is assigned twice to instances"
775 " %s and %s" % (minor, node, instance_a, instance_b))
778 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
781 def _AddIpAddress(ip, name):
782 ips.setdefault(ip, []).append(name)
784 _AddIpAddress(cluster.master_ip, "cluster_ip")
786 for node in data.nodes.values():
787 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
788 if node.secondary_ip != node.primary_ip:
789 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
791 for instance in data.instances.values():
792 for idx, nic in enumerate(instance.nics):
796 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
797 nic_mode = nicparams[constants.NIC_MODE]
798 nic_link = nicparams[constants.NIC_LINK]
800 if nic_mode == constants.NIC_MODE_BRIDGED:
801 link = "bridge:%s" % nic_link
802 elif nic_mode == constants.NIC_MODE_ROUTED:
803 link = "route:%s" % nic_link
805 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
807 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
808 "instance:%s/nic:%d" % (instance.name, idx))
810 for ip, owners in ips.items():
812 result.append("IP address %s is used by multiple owners: %s" %
813 (ip, utils.CommaJoin(owners)))
817 @locking.ssynchronized(_config_lock, shared=1)
818 def VerifyConfig(self):
821 This is just a wrapper over L{_UnlockedVerifyConfig}.
824 @return: a list of error messages; a non-empty list signifies
828 return self._UnlockedVerifyConfig()
830 def _UnlockedSetDiskID(self, disk, node_name):
831 """Convert the unique ID to the ID needed on the target nodes.
833 This is used only for drbd, which needs ip/port configuration.
835 The routine descends down and updates its children also, because
836 this helps when the only the top device is passed to the remote
839 This function is for internal use, when the config lock is already held.
843 for child in disk.children:
844 self._UnlockedSetDiskID(child, node_name)
846 if disk.logical_id is None and disk.physical_id is not None:
848 if disk.dev_type == constants.LD_DRBD8:
849 pnode, snode, port, pminor, sminor, secret = disk.logical_id
850 if node_name not in (pnode, snode):
851 raise errors.ConfigurationError("DRBD device not knowing node %s" %
853 pnode_info = self._UnlockedGetNodeInfo(pnode)
854 snode_info = self._UnlockedGetNodeInfo(snode)
855 if pnode_info is None or snode_info is None:
856 raise errors.ConfigurationError("Can't find primary or secondary node"
857 " for %s" % str(disk))
858 p_data = (pnode_info.secondary_ip, port)
859 s_data = (snode_info.secondary_ip, port)
860 if pnode == node_name:
861 disk.physical_id = p_data + s_data + (pminor, secret)
862 else: # it must be secondary, we tested above
863 disk.physical_id = s_data + p_data + (sminor, secret)
865 disk.physical_id = disk.logical_id
868 @locking.ssynchronized(_config_lock)
869 def SetDiskID(self, disk, node_name):
870 """Convert the unique ID to the ID needed on the target nodes.
872 This is used only for drbd, which needs ip/port configuration.
874 The routine descends down and updates its children also, because
875 this helps when the only the top device is passed to the remote
879 return self._UnlockedSetDiskID(disk, node_name)
881 @locking.ssynchronized(_config_lock)
882 def AddTcpUdpPort(self, port):
883 """Adds a new port to the available port pool.
885 @warning: this method does not "flush" the configuration (via
886 L{_WriteConfig}); callers should do that themselves once the
887 configuration is stable
890 if not isinstance(port, int):
891 raise errors.ProgrammerError("Invalid type passed for port")
893 self._config_data.cluster.tcpudp_port_pool.add(port)
895 @locking.ssynchronized(_config_lock, shared=1)
896 def GetPortList(self):
897 """Returns a copy of the current port list.
900 return self._config_data.cluster.tcpudp_port_pool.copy()
902 @locking.ssynchronized(_config_lock)
903 def AllocatePort(self):
906 The port will be taken from the available port pool or from the
907 default port range (and in this case we increase
911 # If there are TCP/IP ports configured, we use them first.
912 if self._config_data.cluster.tcpudp_port_pool:
913 port = self._config_data.cluster.tcpudp_port_pool.pop()
915 port = self._config_data.cluster.highest_used_port + 1
916 if port >= constants.LAST_DRBD_PORT:
917 raise errors.ConfigurationError("The highest used port is greater"
918 " than %s. Aborting." %
919 constants.LAST_DRBD_PORT)
920 self._config_data.cluster.highest_used_port = port
925 def _UnlockedComputeDRBDMap(self):
926 """Compute the used DRBD minor/nodes.
929 @return: dictionary of node_name: dict of minor: instance_name;
930 the returned dict will have all the nodes in it (even if with
931 an empty list), and a list of duplicates; if the duplicates
932 list is not empty, the configuration is corrupted and its caller
933 should raise an exception
936 def _AppendUsedPorts(instance_name, disk, used):
938 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
939 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
940 for node, port in ((node_a, minor_a), (node_b, minor_b)):
941 assert node in used, ("Node '%s' of instance '%s' not found"
942 " in node list" % (node, instance_name))
943 if port in used[node]:
944 duplicates.append((node, port, instance_name, used[node][port]))
946 used[node][port] = instance_name
948 for child in disk.children:
949 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
953 my_dict = dict((node, {}) for node in self._config_data.nodes)
954 for instance in self._config_data.instances.itervalues():
955 for disk in instance.disks:
956 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
957 for (node, minor), instance in self._temporary_drbds.iteritems():
958 if minor in my_dict[node] and my_dict[node][minor] != instance:
959 duplicates.append((node, minor, instance, my_dict[node][minor]))
961 my_dict[node][minor] = instance
962 return my_dict, duplicates
964 @locking.ssynchronized(_config_lock)
965 def ComputeDRBDMap(self):
966 """Compute the used DRBD minor/nodes.
968 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
970 @return: dictionary of node_name: dict of minor: instance_name;
971 the returned dict will have all the nodes in it (even if with
975 d_map, duplicates = self._UnlockedComputeDRBDMap()
977 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
981 @locking.ssynchronized(_config_lock)
982 def AllocateDRBDMinor(self, nodes, instance):
983 """Allocate a drbd minor.
985 The free minor will be automatically computed from the existing
986 devices. A node can be given multiple times in order to allocate
987 multiple minors. The result is the list of minors, in the same
988 order as the passed nodes.
990 @type instance: string
991 @param instance: the instance for which we allocate minors
994 assert isinstance(instance, basestring), \
995 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
997 d_map, duplicates = self._UnlockedComputeDRBDMap()
999 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1003 ndata = d_map[nname]
1005 # no minors used, we can start at 0
1008 self._temporary_drbds[(nname, 0)] = instance
1012 ffree = utils.FirstFree(keys)
1014 # return the next minor
1015 # TODO: implement high-limit check
1016 minor = keys[-1] + 1
1019 # double-check minor against current instances
1020 assert minor not in d_map[nname], \
1021 ("Attempt to reuse allocated DRBD minor %d on node %s,"
1022 " already allocated to instance %s" %
1023 (minor, nname, d_map[nname][minor]))
1024 ndata[minor] = instance
1025 # double-check minor against reservation
1026 r_key = (nname, minor)
1027 assert r_key not in self._temporary_drbds, \
1028 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1029 " reserved for instance %s" %
1030 (minor, nname, self._temporary_drbds[r_key]))
1031 self._temporary_drbds[r_key] = instance
1032 result.append(minor)
1033 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1037 def _UnlockedReleaseDRBDMinors(self, instance):
1038 """Release temporary drbd minors allocated for a given instance.
1040 @type instance: string
1041 @param instance: the instance for which temporary minors should be
1045 assert isinstance(instance, basestring), \
1046 "Invalid argument passed to ReleaseDRBDMinors"
1047 for key, name in self._temporary_drbds.items():
1048 if name == instance:
1049 del self._temporary_drbds[key]
1051 @locking.ssynchronized(_config_lock)
1052 def ReleaseDRBDMinors(self, instance):
1053 """Release temporary drbd minors allocated for a given instance.
1055 This should be called on the error paths, on the success paths
1056 it's automatically called by the ConfigWriter add and update
1059 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1061 @type instance: string
1062 @param instance: the instance for which temporary minors should be
1066 self._UnlockedReleaseDRBDMinors(instance)
1068 @locking.ssynchronized(_config_lock, shared=1)
1069 def GetConfigVersion(self):
1070 """Get the configuration version.
1072 @return: Config version
1075 return self._config_data.version
1077 @locking.ssynchronized(_config_lock, shared=1)
1078 def GetClusterName(self):
1079 """Get cluster name.
1081 @return: Cluster name
1084 return self._config_data.cluster.cluster_name
1086 @locking.ssynchronized(_config_lock, shared=1)
1087 def GetMasterNode(self):
1088 """Get the hostname of the master node for this cluster.
1090 @return: Master hostname
1093 return self._config_data.cluster.master_node
1095 @locking.ssynchronized(_config_lock, shared=1)
1096 def GetMasterIP(self):
1097 """Get the IP of the master node for this cluster.
1102 return self._config_data.cluster.master_ip
1104 @locking.ssynchronized(_config_lock, shared=1)
1105 def GetMasterNetdev(self):
1106 """Get the master network device for this cluster.
1109 return self._config_data.cluster.master_netdev
1111 @locking.ssynchronized(_config_lock, shared=1)
1112 def GetMasterNetmask(self):
1113 """Get the netmask of the master node for this cluster.
1116 return self._config_data.cluster.master_netmask
1118 @locking.ssynchronized(_config_lock, shared=1)
1119 def GetUseExternalMipScript(self):
1120 """Get flag representing whether to use the external master IP setup script.
1123 return self._config_data.cluster.use_external_mip_script
1125 @locking.ssynchronized(_config_lock, shared=1)
1126 def GetFileStorageDir(self):
1127 """Get the file storage dir for this cluster.
1130 return self._config_data.cluster.file_storage_dir
1132 @locking.ssynchronized(_config_lock, shared=1)
1133 def GetSharedFileStorageDir(self):
1134 """Get the shared file storage dir for this cluster.
1137 return self._config_data.cluster.shared_file_storage_dir
1139 @locking.ssynchronized(_config_lock, shared=1)
1140 def GetHypervisorType(self):
1141 """Get the hypervisor type for this cluster.
1144 return self._config_data.cluster.enabled_hypervisors[0]
1146 @locking.ssynchronized(_config_lock, shared=1)
1147 def GetHostKey(self):
1148 """Return the rsa hostkey from the config.
1151 @return: the rsa hostkey
1154 return self._config_data.cluster.rsahostkeypub
1156 @locking.ssynchronized(_config_lock, shared=1)
1157 def GetDefaultIAllocator(self):
1158 """Get the default instance allocator for this cluster.
1161 return self._config_data.cluster.default_iallocator
1163 @locking.ssynchronized(_config_lock, shared=1)
1164 def GetPrimaryIPFamily(self):
1165 """Get cluster primary ip family.
1167 @return: primary ip family
1170 return self._config_data.cluster.primary_ip_family
1172 @locking.ssynchronized(_config_lock, shared=1)
1173 def GetMasterNetworkParameters(self):
1174 """Get network parameters of the master node.
1176 @rtype: L{object.MasterNetworkParameters}
1177 @return: network parameters of the master node
1180 cluster = self._config_data.cluster
1181 result = objects.MasterNetworkParameters(
1182 name=cluster.master_node, ip=cluster.master_ip,
1183 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1184 ip_family=cluster.primary_ip_family)
1188 @locking.ssynchronized(_config_lock)
1189 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1190 """Add a node group to the configuration.
1192 This method calls group.UpgradeConfig() to fill any missing attributes
1193 according to their default values.
1195 @type group: L{objects.NodeGroup}
1196 @param group: the NodeGroup object to add
1198 @param ec_id: unique id for the job to use when creating a missing UUID
1199 @type check_uuid: bool
1200 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1201 it does, ensure that it does not exist in the
1202 configuration already
1205 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1208 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1209 """Add a node group to the configuration.
1212 logging.info("Adding node group %s to configuration", group.name)
1214 # Some code might need to add a node group with a pre-populated UUID
1215 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1216 # the "does this UUID" exist already check.
1218 self._EnsureUUID(group, ec_id)
1221 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1222 except errors.OpPrereqError:
1225 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1226 " node group (UUID: %s)" %
1227 (group.name, existing_uuid),
1228 errors.ECODE_EXISTS)
1231 group.ctime = group.mtime = time.time()
1232 group.UpgradeConfig()
1234 self._config_data.nodegroups[group.uuid] = group
1235 self._config_data.cluster.serial_no += 1
1237 @locking.ssynchronized(_config_lock)
1238 def RemoveNodeGroup(self, group_uuid):
1239 """Remove a node group from the configuration.
1241 @type group_uuid: string
1242 @param group_uuid: the UUID of the node group to remove
1245 logging.info("Removing node group %s from configuration", group_uuid)
1247 if group_uuid not in self._config_data.nodegroups:
1248 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1250 assert len(self._config_data.nodegroups) != 1, \
1251 "Group '%s' is the only group, cannot be removed" % group_uuid
1253 del self._config_data.nodegroups[group_uuid]
1254 self._config_data.cluster.serial_no += 1
1257 def _UnlockedLookupNodeGroup(self, target):
1258 """Lookup a node group's UUID.
1260 @type target: string or None
1261 @param target: group name or UUID or None to look for the default
1263 @return: nodegroup UUID
1264 @raises errors.OpPrereqError: when the target group cannot be found
1268 if len(self._config_data.nodegroups) != 1:
1269 raise errors.OpPrereqError("More than one node group exists. Target"
1270 " group must be specified explicitly.")
1272 return self._config_data.nodegroups.keys()[0]
1273 if target in self._config_data.nodegroups:
1275 for nodegroup in self._config_data.nodegroups.values():
1276 if nodegroup.name == target:
1277 return nodegroup.uuid
1278 raise errors.OpPrereqError("Node group '%s' not found" % target,
1281 @locking.ssynchronized(_config_lock, shared=1)
1282 def LookupNodeGroup(self, target):
1283 """Lookup a node group's UUID.
1285 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1287 @type target: string or None
1288 @param target: group name or UUID or None to look for the default
1290 @return: nodegroup UUID
1293 return self._UnlockedLookupNodeGroup(target)
1295 def _UnlockedGetNodeGroup(self, uuid):
1296 """Lookup a node group.
1299 @param uuid: group UUID
1300 @rtype: L{objects.NodeGroup} or None
1301 @return: nodegroup object, or None if not found
1304 if uuid not in self._config_data.nodegroups:
1307 return self._config_data.nodegroups[uuid]
1309 @locking.ssynchronized(_config_lock, shared=1)
1310 def GetNodeGroup(self, uuid):
1311 """Lookup a node group.
1314 @param uuid: group UUID
1315 @rtype: L{objects.NodeGroup} or None
1316 @return: nodegroup object, or None if not found
1319 return self._UnlockedGetNodeGroup(uuid)
1321 @locking.ssynchronized(_config_lock, shared=1)
1322 def GetAllNodeGroupsInfo(self):
1323 """Get the configuration of all node groups.
1326 return dict(self._config_data.nodegroups)
1328 @locking.ssynchronized(_config_lock, shared=1)
1329 def GetNodeGroupList(self):
1330 """Get a list of node groups.
1333 return self._config_data.nodegroups.keys()
1335 @locking.ssynchronized(_config_lock, shared=1)
1336 def GetNodeGroupMembersByNodes(self, nodes):
1337 """Get nodes which are member in the same nodegroups as the given nodes.
1340 ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1341 return frozenset(member_name
1342 for node_name in nodes
1344 self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1346 @locking.ssynchronized(_config_lock, shared=1)
1347 def GetMultiNodeGroupInfo(self, group_uuids):
1348 """Get the configuration of multiple node groups.
1350 @param group_uuids: List of node group UUIDs
1352 @return: List of tuples of (group_uuid, group_info)
1355 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1357 @locking.ssynchronized(_config_lock)
1358 def AddInstance(self, instance, ec_id):
1359 """Add an instance to the config.
1361 This should be used after creating a new instance.
1363 @type instance: L{objects.Instance}
1364 @param instance: the instance object
1367 if not isinstance(instance, objects.Instance):
1368 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1370 if instance.disk_template != constants.DT_DISKLESS:
1371 all_lvs = instance.MapLVsByNode()
1372 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1374 all_macs = self._AllMACs()
1375 for nic in instance.nics:
1376 if nic.mac in all_macs:
1377 raise errors.ConfigurationError("Cannot add instance %s:"
1378 " MAC address '%s' already in use." %
1379 (instance.name, nic.mac))
1381 self._EnsureUUID(instance, ec_id)
1383 instance.serial_no = 1
1384 instance.ctime = instance.mtime = time.time()
1385 self._config_data.instances[instance.name] = instance
1386 self._config_data.cluster.serial_no += 1
1387 self._UnlockedReleaseDRBDMinors(instance.name)
1388 self._UnlockedCommitTemporaryIps(ec_id)
1391 def _EnsureUUID(self, item, ec_id):
1392 """Ensures a given object has a valid UUID.
1394 @param item: the instance or node to be checked
1395 @param ec_id: the execution context id for the uuid reservation
1399 item.uuid = self._GenerateUniqueID(ec_id)
1400 elif item.uuid in self._AllIDs(include_temporary=True):
1401 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1402 " in use" % (item.name, item.uuid))
1404 def _SetInstanceStatus(self, instance_name, status):
1405 """Set the instance's status to a given value.
1408 assert status in constants.ADMINST_ALL, \
1409 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1411 if instance_name not in self._config_data.instances:
1412 raise errors.ConfigurationError("Unknown instance '%s'" %
1414 instance = self._config_data.instances[instance_name]
1415 if instance.admin_state != status:
1416 instance.admin_state = status
1417 instance.serial_no += 1
1418 instance.mtime = time.time()
1421 @locking.ssynchronized(_config_lock)
1422 def MarkInstanceUp(self, instance_name):
1423 """Mark the instance status to up in the config.
1426 self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1428 @locking.ssynchronized(_config_lock)
1429 def MarkInstanceOffline(self, instance_name):
1430 """Mark the instance status to down in the config.
1433 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1435 @locking.ssynchronized(_config_lock)
1436 def RemoveInstance(self, instance_name):
1437 """Remove the instance from the configuration.
1440 if instance_name not in self._config_data.instances:
1441 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1443 # If a network port has been allocated to the instance,
1444 # return it to the pool of free ports.
1445 inst = self._config_data.instances[instance_name]
1446 network_port = getattr(inst, "network_port", None)
1447 if network_port is not None:
1448 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1450 instance = self._UnlockedGetInstanceInfo(instance_name)
1452 for nic in instance.nics:
1453 if nic.network and nic.ip:
1454 # Return all IP addresses to the respective address pools
1455 self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1457 del self._config_data.instances[instance_name]
1458 self._config_data.cluster.serial_no += 1
1461 @locking.ssynchronized(_config_lock)
1462 def RenameInstance(self, old_name, new_name):
1463 """Rename an instance.
1465 This needs to be done in ConfigWriter and not by RemoveInstance
1466 combined with AddInstance as only we can guarantee an atomic
1470 if old_name not in self._config_data.instances:
1471 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1473 # Operate on a copy to not loose instance object in case of a failure
1474 inst = self._config_data.instances[old_name].Copy()
1475 inst.name = new_name
1477 for (idx, disk) in enumerate(inst.disks):
1478 if disk.dev_type == constants.LD_FILE:
1479 # rename the file paths in logical and physical id
1480 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1481 disk.logical_id = (disk.logical_id[0],
1482 utils.PathJoin(file_storage_dir, inst.name,
1484 disk.physical_id = disk.logical_id
1486 # Actually replace instance object
1487 del self._config_data.instances[old_name]
1488 self._config_data.instances[inst.name] = inst
1490 # Force update of ssconf files
1491 self._config_data.cluster.serial_no += 1
1495 @locking.ssynchronized(_config_lock)
1496 def MarkInstanceDown(self, instance_name):
1497 """Mark the status of an instance to down in the configuration.
1500 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1502 def _UnlockedGetInstanceList(self):
1503 """Get the list of instances.
1505 This function is for internal use, when the config lock is already held.
1508 return self._config_data.instances.keys()
1510 @locking.ssynchronized(_config_lock, shared=1)
1511 def GetInstanceList(self):
1512 """Get the list of instances.
1514 @return: array of instances, ex. ['instance2.example.com',
1515 'instance1.example.com']
1518 return self._UnlockedGetInstanceList()
1520 def ExpandInstanceName(self, short_name):
1521 """Attempt to expand an incomplete instance name.
1524 # Locking is done in L{ConfigWriter.GetInstanceList}
1525 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1527 def _UnlockedGetInstanceInfo(self, instance_name):
1528 """Returns information about an instance.
1530 This function is for internal use, when the config lock is already held.
1533 if instance_name not in self._config_data.instances:
1536 return self._config_data.instances[instance_name]
1538 @locking.ssynchronized(_config_lock, shared=1)
1539 def GetInstanceInfo(self, instance_name):
1540 """Returns information about an instance.
1542 It takes the information from the configuration file. Other information of
1543 an instance are taken from the live systems.
1545 @param instance_name: name of the instance, e.g.
1546 I{instance1.example.com}
1548 @rtype: L{objects.Instance}
1549 @return: the instance object
1552 return self._UnlockedGetInstanceInfo(instance_name)
1554 @locking.ssynchronized(_config_lock, shared=1)
1555 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1556 """Returns set of node group UUIDs for instance's nodes.
1561 instance = self._UnlockedGetInstanceInfo(instance_name)
1563 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1566 nodes = [instance.primary_node]
1568 nodes = instance.all_nodes
1570 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1571 for node_name in nodes)
1573 @locking.ssynchronized(_config_lock, shared=1)
1574 def GetInstanceNetworks(self, instance_name):
1575 """Returns set of network UUIDs for instance's nics.
1580 instance = self._UnlockedGetInstanceInfo(instance_name)
1582 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1585 for nic in instance.nics:
1587 networks.add(nic.network)
1589 return frozenset(networks)
1591 @locking.ssynchronized(_config_lock, shared=1)
1592 def GetMultiInstanceInfo(self, instances):
1593 """Get the configuration of multiple instances.
1595 @param instances: list of instance names
1597 @return: list of tuples (instance, instance_info), where
1598 instance_info is what would GetInstanceInfo return for the
1599 node, while keeping the original order
1602 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1604 @locking.ssynchronized(_config_lock, shared=1)
1605 def GetAllInstancesInfo(self):
1606 """Get the configuration of all instances.
1609 @return: dict of (instance, instance_info), where instance_info is what
1610 would GetInstanceInfo return for the node
1613 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1614 for instance in self._UnlockedGetInstanceList()])
1617 @locking.ssynchronized(_config_lock, shared=1)
1618 def GetInstancesInfoByFilter(self, filter_fn):
1619 """Get instance configuration with a filter.
1621 @type filter_fn: callable
1622 @param filter_fn: Filter function receiving instance object as parameter,
1623 returning boolean. Important: this function is called while the
1624 configuration locks is held. It must not do any complex work or call
1625 functions potentially leading to a deadlock. Ideally it doesn't call any
1626 other functions and just compares instance attributes.
1629 return dict((name, inst)
1630 for (name, inst) in self._config_data.instances.items()
1633 @locking.ssynchronized(_config_lock)
1634 def AddNode(self, node, ec_id):
1635 """Add a node to the configuration.
1637 @type node: L{objects.Node}
1638 @param node: a Node instance
1641 logging.info("Adding node %s to configuration", node.name)
1643 self._EnsureUUID(node, ec_id)
1646 node.ctime = node.mtime = time.time()
1647 self._UnlockedAddNodeToGroup(node.name, node.group)
1648 self._config_data.nodes[node.name] = node
1649 self._config_data.cluster.serial_no += 1
1652 @locking.ssynchronized(_config_lock)
1653 def RemoveNode(self, node_name):
1654 """Remove a node from the configuration.
1657 logging.info("Removing node %s from configuration", node_name)
1659 if node_name not in self._config_data.nodes:
1660 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1662 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1663 del self._config_data.nodes[node_name]
1664 self._config_data.cluster.serial_no += 1
1667 def ExpandNodeName(self, short_name):
1668 """Attempt to expand an incomplete node name.
1671 # Locking is done in L{ConfigWriter.GetNodeList}
1672 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1674 def _UnlockedGetNodeInfo(self, node_name):
1675 """Get the configuration of a node, as stored in the config.
1677 This function is for internal use, when the config lock is already
1680 @param node_name: the node name, e.g. I{node1.example.com}
1682 @rtype: L{objects.Node}
1683 @return: the node object
1686 if node_name not in self._config_data.nodes:
1689 return self._config_data.nodes[node_name]
1691 @locking.ssynchronized(_config_lock, shared=1)
1692 def GetNodeInfo(self, node_name):
1693 """Get the configuration of a node, as stored in the config.
1695 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1697 @param node_name: the node name, e.g. I{node1.example.com}
1699 @rtype: L{objects.Node}
1700 @return: the node object
1703 return self._UnlockedGetNodeInfo(node_name)
1705 @locking.ssynchronized(_config_lock, shared=1)
1706 def GetNodeInstances(self, node_name):
1707 """Get the instances of a node, as stored in the config.
1709 @param node_name: the node name, e.g. I{node1.example.com}
1711 @rtype: (list, list)
1712 @return: a tuple with two lists: the primary and the secondary instances
1717 for inst in self._config_data.instances.values():
1718 if inst.primary_node == node_name:
1719 pri.append(inst.name)
1720 if node_name in inst.secondary_nodes:
1721 sec.append(inst.name)
1724 @locking.ssynchronized(_config_lock, shared=1)
1725 def GetNodeGroupInstances(self, uuid, primary_only=False):
1726 """Get the instances of a node group.
1728 @param uuid: Node group UUID
1729 @param primary_only: Whether to only consider primary nodes
1731 @return: List of instance names in node group
1735 nodes_fn = lambda inst: [inst.primary_node]
1737 nodes_fn = lambda inst: inst.all_nodes
1739 return frozenset(inst.name
1740 for inst in self._config_data.instances.values()
1741 for node_name in nodes_fn(inst)
1742 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1744 def _UnlockedGetNodeList(self):
1745 """Return the list of nodes which are in the configuration.
1747 This function is for internal use, when the config lock is already
1753 return self._config_data.nodes.keys()
1755 @locking.ssynchronized(_config_lock, shared=1)
1756 def GetNodeList(self):
1757 """Return the list of nodes which are in the configuration.
1760 return self._UnlockedGetNodeList()
1762 def _UnlockedGetOnlineNodeList(self):
1763 """Return the list of nodes which are online.
1766 all_nodes = [self._UnlockedGetNodeInfo(node)
1767 for node in self._UnlockedGetNodeList()]
1768 return [node.name for node in all_nodes if not node.offline]
1770 @locking.ssynchronized(_config_lock, shared=1)
1771 def GetOnlineNodeList(self):
1772 """Return the list of nodes which are online.
1775 return self._UnlockedGetOnlineNodeList()
1777 @locking.ssynchronized(_config_lock, shared=1)
1778 def GetVmCapableNodeList(self):
1779 """Return the list of nodes which are not vm capable.
1782 all_nodes = [self._UnlockedGetNodeInfo(node)
1783 for node in self._UnlockedGetNodeList()]
1784 return [node.name for node in all_nodes if node.vm_capable]
1786 @locking.ssynchronized(_config_lock, shared=1)
1787 def GetNonVmCapableNodeList(self):
1788 """Return the list of nodes which are not vm capable.
1791 all_nodes = [self._UnlockedGetNodeInfo(node)
1792 for node in self._UnlockedGetNodeList()]
1793 return [node.name for node in all_nodes if not node.vm_capable]
1795 @locking.ssynchronized(_config_lock, shared=1)
1796 def GetMultiNodeInfo(self, nodes):
1797 """Get the configuration of multiple nodes.
1799 @param nodes: list of node names
1801 @return: list of tuples of (node, node_info), where node_info is
1802 what would GetNodeInfo return for the node, in the original
1806 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1808 @locking.ssynchronized(_config_lock, shared=1)
1809 def GetAllNodesInfo(self):
1810 """Get the configuration of all nodes.
1813 @return: dict of (node, node_info), where node_info is what
1814 would GetNodeInfo return for the node
1817 return self._UnlockedGetAllNodesInfo()
1819 def _UnlockedGetAllNodesInfo(self):
1820 """Gets configuration of all nodes.
1822 @note: See L{GetAllNodesInfo}
1825 return dict([(node, self._UnlockedGetNodeInfo(node))
1826 for node in self._UnlockedGetNodeList()])
1828 @locking.ssynchronized(_config_lock, shared=1)
1829 def GetNodeGroupsFromNodes(self, nodes):
1830 """Returns groups for a list of nodes.
1832 @type nodes: list of string
1833 @param nodes: List of node names
1837 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1839 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1840 """Get the number of current and maximum desired and possible candidates.
1842 @type exceptions: list
1843 @param exceptions: if passed, list of nodes that should be ignored
1845 @return: tuple of (current, desired and possible, possible)
1848 mc_now = mc_should = mc_max = 0
1849 for node in self._config_data.nodes.values():
1850 if exceptions and node.name in exceptions:
1852 if not (node.offline or node.drained) and node.master_capable:
1854 if node.master_candidate:
1856 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1857 return (mc_now, mc_should, mc_max)
1859 @locking.ssynchronized(_config_lock, shared=1)
1860 def GetMasterCandidateStats(self, exceptions=None):
1861 """Get the number of current and maximum possible candidates.
1863 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1865 @type exceptions: list
1866 @param exceptions: if passed, list of nodes that should be ignored
1868 @return: tuple of (current, max)
1871 return self._UnlockedGetMasterCandidateStats(exceptions)
1873 @locking.ssynchronized(_config_lock)
1874 def MaintainCandidatePool(self, exceptions):
1875 """Try to grow the candidate pool to the desired size.
1877 @type exceptions: list
1878 @param exceptions: if passed, list of nodes that should be ignored
1880 @return: list with the adjusted nodes (L{objects.Node} instances)
1883 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1886 node_list = self._config_data.nodes.keys()
1887 random.shuffle(node_list)
1888 for name in node_list:
1889 if mc_now >= mc_max:
1891 node = self._config_data.nodes[name]
1892 if (node.master_candidate or node.offline or node.drained or
1893 node.name in exceptions or not node.master_capable):
1895 mod_list.append(node)
1896 node.master_candidate = True
1899 if mc_now != mc_max:
1900 # this should not happen
1901 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1902 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1904 self._config_data.cluster.serial_no += 1
1909 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1910 """Add a given node to the specified group.
1913 if nodegroup_uuid not in self._config_data.nodegroups:
1914 # This can happen if a node group gets deleted between its lookup and
1915 # when we're adding the first node to it, since we don't keep a lock in
1916 # the meantime. It's ok though, as we'll fail cleanly if the node group
1917 # is not found anymore.
1918 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1919 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1920 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1922 def _UnlockedRemoveNodeFromGroup(self, node):
1923 """Remove a given node from its group.
1926 nodegroup = node.group
1927 if nodegroup not in self._config_data.nodegroups:
1928 logging.warning("Warning: node '%s' has unknown node group '%s'"
1929 " (while being removed from it)", node.name, nodegroup)
1930 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1931 if node.name not in nodegroup_obj.members:
1932 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1933 " (while being removed from it)", node.name, nodegroup)
1935 nodegroup_obj.members.remove(node.name)
1937 @locking.ssynchronized(_config_lock)
1938 def AssignGroupNodes(self, mods):
1939 """Changes the group of a number of nodes.
1941 @type mods: list of tuples; (node name, new group UUID)
1942 @param mods: Node membership modifications
1945 groups = self._config_data.nodegroups
1946 nodes = self._config_data.nodes
1950 # Try to resolve names/UUIDs first
1951 for (node_name, new_group_uuid) in mods:
1953 node = nodes[node_name]
1955 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1957 if node.group == new_group_uuid:
1958 # Node is being assigned to its current group
1959 logging.debug("Node '%s' was assigned to its current group (%s)",
1960 node_name, node.group)
1963 # Try to find current group of node
1965 old_group = groups[node.group]
1967 raise errors.ConfigurationError("Unable to find old group '%s'" %
1970 # Try to find new group for node
1972 new_group = groups[new_group_uuid]
1974 raise errors.ConfigurationError("Unable to find new group '%s'" %
1977 assert node.name in old_group.members, \
1978 ("Inconsistent configuration: node '%s' not listed in members for its"
1979 " old group '%s'" % (node.name, old_group.uuid))
1980 assert node.name not in new_group.members, \
1981 ("Inconsistent configuration: node '%s' already listed in members for"
1982 " its new group '%s'" % (node.name, new_group.uuid))
1984 resmod.append((node, old_group, new_group))
1987 for (node, old_group, new_group) in resmod:
1988 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1989 "Assigning to current group is not possible"
1991 node.group = new_group.uuid
1993 # Update members of involved groups
1994 if node.name in old_group.members:
1995 old_group.members.remove(node.name)
1996 if node.name not in new_group.members:
1997 new_group.members.append(node.name)
1999 # Update timestamps and serials (only once per node/group object)
2001 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2005 # Force ssconf update
2006 self._config_data.cluster.serial_no += 1
2010 def _BumpSerialNo(self):
2011 """Bump up the serial number of the config.
2014 self._config_data.serial_no += 1
2015 self._config_data.mtime = time.time()
2017 def _AllUUIDObjects(self):
2018 """Returns all objects with uuid attributes.
2021 return (self._config_data.instances.values() +
2022 self._config_data.nodes.values() +
2023 self._config_data.nodegroups.values() +
2024 self._config_data.networks.values() +
2025 [self._config_data.cluster])
2027 def _OpenConfig(self, accept_foreign):
2028 """Read the config data from disk.
2031 raw_data = utils.ReadFile(self._cfg_file)
2034 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2035 except Exception, err:
2036 raise errors.ConfigurationError(err)
2038 # Make sure the configuration has the right version
2039 _ValidateConfig(data)
2041 if (not hasattr(data, "cluster") or
2042 not hasattr(data.cluster, "rsahostkeypub")):
2043 raise errors.ConfigurationError("Incomplete configuration"
2044 " (missing cluster.rsahostkeypub)")
2046 if data.cluster.master_node != self._my_hostname and not accept_foreign:
2047 msg = ("The configuration denotes node %s as master, while my"
2048 " hostname is %s; opening a foreign configuration is only"
2049 " possible in accept_foreign mode" %
2050 (data.cluster.master_node, self._my_hostname))
2051 raise errors.ConfigurationError(msg)
2053 self._config_data = data
2054 # reset the last serial as -1 so that the next write will cause
2056 self._last_cluster_serial = -1
2058 # Upgrade configuration if needed
2059 self._UpgradeConfig()
2061 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2063 def _UpgradeConfig(self):
2064 """Run any upgrade steps.
2066 This method performs both in-object upgrades and also update some data
2067 elements that need uniqueness across the whole configuration or interact
2070 @warning: this function will call L{_WriteConfig()}, but also
2071 L{DropECReservations} so it needs to be called only from a
2072 "safe" place (the constructor). If one wanted to call it with
2073 the lock held, a DropECReservationUnlocked would need to be
2074 created first, to avoid causing deadlock.
2077 # Keep a copy of the persistent part of _config_data to check for changes
2078 # Serialization doesn't guarantee order in dictionaries
2079 oldconf = copy.deepcopy(self._config_data.ToDict())
2081 # In-object upgrades
2082 self._config_data.UpgradeConfig()
2084 for item in self._AllUUIDObjects():
2085 if item.uuid is None:
2086 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2087 if not self._config_data.nodegroups:
2088 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2089 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2091 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2092 for node in self._config_data.nodes.values():
2094 node.group = self.LookupNodeGroup(None)
2095 # This is technically *not* an upgrade, but needs to be done both when
2096 # nodegroups are being added, and upon normally loading the config,
2097 # because the members list of a node group is discarded upon
2098 # serializing/deserializing the object.
2099 self._UnlockedAddNodeToGroup(node.name, node.group)
2101 modified = (oldconf != self._config_data.ToDict())
2104 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2105 # only called at config init time, without the lock held
2106 self.DropECReservations(_UPGRADE_CONFIG_JID)
2108 def _DistributeConfig(self, feedback_fn):
2109 """Distribute the configuration to the other nodes.
2111 Currently, this only copies the configuration file. In the future,
2112 it could be used to encapsulate the 2/3-phase update mechanism.
2122 myhostname = self._my_hostname
2123 # we can skip checking whether _UnlockedGetNodeInfo returns None
2124 # since the node list comes from _UnlocketGetNodeList, and we are
2125 # called with the lock held, so no modifications should take place
2127 for node_name in self._UnlockedGetNodeList():
2128 if node_name == myhostname:
2130 node_info = self._UnlockedGetNodeInfo(node_name)
2131 if not node_info.master_candidate:
2133 node_list.append(node_info.name)
2134 addr_list.append(node_info.primary_ip)
2136 # TODO: Use dedicated resolver talking to config writer for name resolution
2138 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2139 for to_node, to_result in result.items():
2140 msg = to_result.fail_msg
2142 msg = ("Copy of file %s to node %s failed: %s" %
2143 (self._cfg_file, to_node, msg))
2153 def _WriteConfig(self, destination=None, feedback_fn=None):
2154 """Write the configuration data to persistent storage.
2157 assert feedback_fn is None or callable(feedback_fn)
2159 # Warn on config errors, but don't abort the save - the
2160 # configuration has already been modified, and we can't revert;
2161 # the best we can do is to warn the user and save as is, leaving
2162 # recovery to the user
2163 config_errors = self._UnlockedVerifyConfig()
2165 errmsg = ("Configuration data is not consistent: %s" %
2166 (utils.CommaJoin(config_errors)))
2167 logging.critical(errmsg)
2171 if destination is None:
2172 destination = self._cfg_file
2173 self._BumpSerialNo()
2174 txt = serializer.Dump(self._config_data.ToDict())
2176 getents = self._getents()
2178 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2179 close=False, gid=getents.confd_gid, mode=0640)
2180 except errors.LockError:
2181 raise errors.ConfigurationError("The configuration file has been"
2182 " modified since the last write, cannot"
2185 self._cfg_id = utils.GetFileID(fd=fd)
2189 self.write_count += 1
2191 # and redistribute the config file to master candidates
2192 self._DistributeConfig(feedback_fn)
2194 # Write ssconf files on all nodes (including locally)
2195 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2196 if not self._offline:
2197 result = self._GetRpc(None).call_write_ssconf_files(
2198 self._UnlockedGetOnlineNodeList(),
2199 self._UnlockedGetSsconfValues())
2201 for nname, nresu in result.items():
2202 msg = nresu.fail_msg
2204 errmsg = ("Error while uploading ssconf files to"
2205 " node %s: %s" % (nname, msg))
2206 logging.warning(errmsg)
2211 self._last_cluster_serial = self._config_data.cluster.serial_no
2213 def _UnlockedGetSsconfValues(self):
2214 """Return the values needed by ssconf.
2217 @return: a dictionary with keys the ssconf names and values their
2222 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2223 node_names = utils.NiceSort(self._UnlockedGetNodeList())
2224 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2225 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2226 for ninfo in node_info]
2227 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2228 for ninfo in node_info]
2230 instance_data = fn(instance_names)
2231 off_data = fn(node.name for node in node_info if node.offline)
2232 on_data = fn(node.name for node in node_info if not node.offline)
2233 mc_data = fn(node.name for node in node_info if node.master_candidate)
2234 mc_ips_data = fn(node.primary_ip for node in node_info
2235 if node.master_candidate)
2236 node_data = fn(node_names)
2237 node_pri_ips_data = fn(node_pri_ips)
2238 node_snd_ips_data = fn(node_snd_ips)
2240 cluster = self._config_data.cluster
2241 cluster_tags = fn(cluster.GetTags())
2243 hypervisor_list = fn(cluster.enabled_hypervisors)
2245 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2247 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2248 self._config_data.nodegroups.values()]
2249 nodegroups_data = fn(utils.NiceSort(nodegroups))
2250 networks = ["%s %s" % (net.uuid, net.name) for net in
2251 self._config_data.networks.values()]
2252 networks_data = fn(utils.NiceSort(networks))
2255 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2256 constants.SS_CLUSTER_TAGS: cluster_tags,
2257 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2258 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2259 constants.SS_MASTER_CANDIDATES: mc_data,
2260 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2261 constants.SS_MASTER_IP: cluster.master_ip,
2262 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2263 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2264 constants.SS_MASTER_NODE: cluster.master_node,
2265 constants.SS_NODE_LIST: node_data,
2266 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2267 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2268 constants.SS_OFFLINE_NODES: off_data,
2269 constants.SS_ONLINE_NODES: on_data,
2270 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2271 constants.SS_INSTANCE_LIST: instance_data,
2272 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2273 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2274 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2275 constants.SS_UID_POOL: uid_pool,
2276 constants.SS_NODEGROUPS: nodegroups_data,
2277 constants.SS_NETWORKS: networks_data,
2279 bad_values = [(k, v) for k, v in ssconf_values.items()
2280 if not isinstance(v, (str, basestring))]
2282 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2283 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2284 " values: %s" % err)
2285 return ssconf_values
2287 @locking.ssynchronized(_config_lock, shared=1)
2288 def GetSsconfValues(self):
2289 """Wrapper using lock around _UnlockedGetSsconf().
2292 return self._UnlockedGetSsconfValues()
2294 @locking.ssynchronized(_config_lock, shared=1)
2295 def GetVGName(self):
2296 """Return the volume group name.
2299 return self._config_data.cluster.volume_group_name
2301 @locking.ssynchronized(_config_lock)
2302 def SetVGName(self, vg_name):
2303 """Set the volume group name.
2306 self._config_data.cluster.volume_group_name = vg_name
2307 self._config_data.cluster.serial_no += 1
2310 @locking.ssynchronized(_config_lock, shared=1)
2311 def GetDRBDHelper(self):
2312 """Return DRBD usermode helper.
2315 return self._config_data.cluster.drbd_usermode_helper
2317 @locking.ssynchronized(_config_lock)
2318 def SetDRBDHelper(self, drbd_helper):
2319 """Set DRBD usermode helper.
2322 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2323 self._config_data.cluster.serial_no += 1
2326 @locking.ssynchronized(_config_lock, shared=1)
2327 def GetMACPrefix(self):
2328 """Return the mac prefix.
2331 return self._config_data.cluster.mac_prefix
2333 @locking.ssynchronized(_config_lock, shared=1)
2334 def GetClusterInfo(self):
2335 """Returns information about the cluster
2337 @rtype: L{objects.Cluster}
2338 @return: the cluster object
2341 return self._config_data.cluster
2343 @locking.ssynchronized(_config_lock, shared=1)
2344 def HasAnyDiskOfType(self, dev_type):
2345 """Check if in there is at disk of the given type in the configuration.
2348 return self._config_data.HasAnyDiskOfType(dev_type)
2350 @locking.ssynchronized(_config_lock)
2351 def Update(self, target, feedback_fn, ec_id=None):
2352 """Notify function to be called after updates.
2354 This function must be called when an object (as returned by
2355 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2356 caller wants the modifications saved to the backing store. Note
2357 that all modified objects will be saved, but the target argument
2358 is the one the caller wants to ensure that it's saved.
2360 @param target: an instance of either L{objects.Cluster},
2361 L{objects.Node} or L{objects.Instance} which is existing in
2363 @param feedback_fn: Callable feedback function
2366 if self._config_data is None:
2367 raise errors.ProgrammerError("Configuration file not read,"
2369 update_serial = False
2370 if isinstance(target, objects.Cluster):
2371 test = target == self._config_data.cluster
2372 elif isinstance(target, objects.Node):
2373 test = target in self._config_data.nodes.values()
2374 update_serial = True
2375 elif isinstance(target, objects.Instance):
2376 test = target in self._config_data.instances.values()
2377 elif isinstance(target, objects.NodeGroup):
2378 test = target in self._config_data.nodegroups.values()
2379 elif isinstance(target, objects.Network):
2380 test = target in self._config_data.networks.values()
2382 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2383 " ConfigWriter.Update" % type(target))
2385 raise errors.ConfigurationError("Configuration updated since object"
2386 " has been read or unknown object")
2387 target.serial_no += 1
2388 target.mtime = now = time.time()
2391 # for node updates, we need to increase the cluster serial too
2392 self._config_data.cluster.serial_no += 1
2393 self._config_data.cluster.mtime = now
2395 if isinstance(target, objects.Instance):
2396 self._UnlockedReleaseDRBDMinors(target.name)
2398 if ec_id is not None:
2399 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2400 self._UnlockedCommitTemporaryIps(ec_id)
2402 self._WriteConfig(feedback_fn=feedback_fn)
2404 @locking.ssynchronized(_config_lock)
2405 def DropECReservations(self, ec_id):
2406 """Drop per-execution-context reservations
2409 for rm in self._all_rms:
2410 rm.DropECReservations(ec_id)
2412 @locking.ssynchronized(_config_lock, shared=1)
2413 def GetAllNetworksInfo(self):
2414 """Get configuration info of all the networks.
2417 return dict(self._config_data.networks)
2419 def _UnlockedGetNetworkList(self):
2420 """Get the list of networks.
2422 This function is for internal use, when the config lock is already held.
2425 return self._config_data.networks.keys()
2427 @locking.ssynchronized(_config_lock, shared=1)
2428 def GetNetworkList(self):
2429 """Get the list of networks.
2431 @return: array of networks, ex. ["main", "vlan100", "200]
2434 return self._UnlockedGetNetworkList()
2436 @locking.ssynchronized(_config_lock, shared=1)
2437 def GetNetworkNames(self):
2438 """Get a list of network names
2442 for net in self._config_data.networks.values()]
2445 def _UnlockedGetNetwork(self, uuid):
2446 """Returns information about a network.
2448 This function is for internal use, when the config lock is already held.
2451 if uuid not in self._config_data.networks:
2454 return self._config_data.networks[uuid]
2456 @locking.ssynchronized(_config_lock, shared=1)
2457 def GetNetwork(self, uuid):
2458 """Returns information about a network.
2460 It takes the information from the configuration file.
2462 @param uuid: UUID of the network
2464 @rtype: L{objects.Network}
2465 @return: the network object
2468 return self._UnlockedGetNetwork(uuid)
2470 @locking.ssynchronized(_config_lock)
2471 def AddNetwork(self, net, ec_id, check_uuid=True):
2472 """Add a network to the configuration.
2474 @type net: L{objects.Network}
2475 @param net: the Network object to add
2477 @param ec_id: unique id for the job to use when creating a missing UUID
2480 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2483 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2484 """Add a network to the configuration.
2487 logging.info("Adding network %s to configuration", net.name)
2490 self._EnsureUUID(net, ec_id)
2493 self._config_data.networks[net.uuid] = net
2494 self._config_data.cluster.serial_no += 1
2496 def _UnlockedLookupNetwork(self, target):
2497 """Lookup a network's UUID.
2499 @type target: string
2500 @param target: network name or UUID
2502 @return: network UUID
2503 @raises errors.OpPrereqError: when the target network cannot be found
2508 if target in self._config_data.networks:
2510 for net in self._config_data.networks.values():
2511 if net.name == target:
2513 raise errors.OpPrereqError("Network '%s' not found" % target,
2516 @locking.ssynchronized(_config_lock, shared=1)
2517 def LookupNetwork(self, target):
2518 """Lookup a network's UUID.
2520 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2522 @type target: string
2523 @param target: network name or UUID
2525 @return: network UUID
2528 return self._UnlockedLookupNetwork(target)
2530 @locking.ssynchronized(_config_lock)
2531 def RemoveNetwork(self, network_uuid):
2532 """Remove a network from the configuration.
2534 @type network_uuid: string
2535 @param network_uuid: the UUID of the network to remove
2538 logging.info("Removing network %s from configuration", network_uuid)
2540 if network_uuid not in self._config_data.networks:
2541 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2543 del self._config_data.networks[network_uuid]
2544 self._config_data.cluster.serial_no += 1
2547 def _UnlockedGetGroupNetParams(self, net_uuid, node):
2548 """Get the netparams (mode, link) of a network.
2550 Get a network's netparams for a given node.
2552 @type net_uuid: string
2553 @param net_uuid: network uuid
2555 @param node: node name
2556 @rtype: dict or None
2560 node_info = self._UnlockedGetNodeInfo(node)
2561 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2562 netparams = nodegroup_info.networks.get(net_uuid, None)
2566 @locking.ssynchronized(_config_lock, shared=1)
2567 def GetGroupNetParams(self, net_uuid, node):
2568 """Locking wrapper of _UnlockedGetGroupNetParams()
2571 return self._UnlockedGetGroupNetParams(net_uuid, node)
2573 @locking.ssynchronized(_config_lock, shared=1)
2574 def CheckIPInNodeGroup(self, ip, node):
2575 """Check IP uniqueness in nodegroup.
2577 Check networks that are connected in the node's node group
2578 if ip is contained in any of them. Used when creating/adding
2579 a NIC to ensure uniqueness among nodegroups.
2582 @param ip: ip address
2584 @param node: node name
2585 @rtype: (string, dict) or (None, None)
2586 @return: (network name, netparams)
2591 node_info = self._UnlockedGetNodeInfo(node)
2592 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2593 for net_uuid in nodegroup_info.networks.keys():
2594 net_info = self._UnlockedGetNetwork(net_uuid)
2595 pool = network.AddressPool(net_info)
2596 if pool.Contains(ip):
2597 return (net_info.name, nodegroup_info.networks[net_uuid])