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 is not None and nic.ip is not None:
1454 net_uuid = self._UnlockedLookupNetwork(nic.network)
1455 # Return all IP addresses to the respective address pools
1456 self._UnlockedCommitIp(constants.RELEASE_ACTION, net_uuid, nic.ip)
1458 del self._config_data.instances[instance_name]
1459 self._config_data.cluster.serial_no += 1
1462 @locking.ssynchronized(_config_lock)
1463 def RenameInstance(self, old_name, new_name):
1464 """Rename an instance.
1466 This needs to be done in ConfigWriter and not by RemoveInstance
1467 combined with AddInstance as only we can guarantee an atomic
1471 if old_name not in self._config_data.instances:
1472 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1474 # Operate on a copy to not loose instance object in case of a failure
1475 inst = self._config_data.instances[old_name].Copy()
1476 inst.name = new_name
1478 for (idx, disk) in enumerate(inst.disks):
1479 if disk.dev_type == constants.LD_FILE:
1480 # rename the file paths in logical and physical id
1481 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1482 disk.logical_id = (disk.logical_id[0],
1483 utils.PathJoin(file_storage_dir, inst.name,
1485 disk.physical_id = disk.logical_id
1487 # Actually replace instance object
1488 del self._config_data.instances[old_name]
1489 self._config_data.instances[inst.name] = inst
1491 # Force update of ssconf files
1492 self._config_data.cluster.serial_no += 1
1496 @locking.ssynchronized(_config_lock)
1497 def MarkInstanceDown(self, instance_name):
1498 """Mark the status of an instance to down in the configuration.
1501 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1503 def _UnlockedGetInstanceList(self):
1504 """Get the list of instances.
1506 This function is for internal use, when the config lock is already held.
1509 return self._config_data.instances.keys()
1511 @locking.ssynchronized(_config_lock, shared=1)
1512 def GetInstanceList(self):
1513 """Get the list of instances.
1515 @return: array of instances, ex. ['instance2.example.com',
1516 'instance1.example.com']
1519 return self._UnlockedGetInstanceList()
1521 def ExpandInstanceName(self, short_name):
1522 """Attempt to expand an incomplete instance name.
1525 # Locking is done in L{ConfigWriter.GetInstanceList}
1526 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1528 def _UnlockedGetInstanceInfo(self, instance_name):
1529 """Returns information about an instance.
1531 This function is for internal use, when the config lock is already held.
1534 if instance_name not in self._config_data.instances:
1537 return self._config_data.instances[instance_name]
1539 @locking.ssynchronized(_config_lock, shared=1)
1540 def GetInstanceInfo(self, instance_name):
1541 """Returns information about an instance.
1543 It takes the information from the configuration file. Other information of
1544 an instance are taken from the live systems.
1546 @param instance_name: name of the instance, e.g.
1547 I{instance1.example.com}
1549 @rtype: L{objects.Instance}
1550 @return: the instance object
1553 return self._UnlockedGetInstanceInfo(instance_name)
1555 @locking.ssynchronized(_config_lock, shared=1)
1556 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1557 """Returns set of node group UUIDs for instance's nodes.
1562 instance = self._UnlockedGetInstanceInfo(instance_name)
1564 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1567 nodes = [instance.primary_node]
1569 nodes = instance.all_nodes
1571 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1572 for node_name in nodes)
1574 @locking.ssynchronized(_config_lock, shared=1)
1575 def GetInstanceNetworks(self, instance_name):
1576 """Returns set of network UUIDs for instance's nics.
1581 instance = self._UnlockedGetInstanceInfo(instance_name)
1583 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1586 for nic in instance.nics:
1588 networks.add(nic.network)
1590 return frozenset(networks)
1592 @locking.ssynchronized(_config_lock, shared=1)
1593 def GetMultiInstanceInfo(self, instances):
1594 """Get the configuration of multiple instances.
1596 @param instances: list of instance names
1598 @return: list of tuples (instance, instance_info), where
1599 instance_info is what would GetInstanceInfo return for the
1600 node, while keeping the original order
1603 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1605 @locking.ssynchronized(_config_lock, shared=1)
1606 def GetAllInstancesInfo(self):
1607 """Get the configuration of all instances.
1610 @return: dict of (instance, instance_info), where instance_info is what
1611 would GetInstanceInfo return for the node
1614 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1615 for instance in self._UnlockedGetInstanceList()])
1618 @locking.ssynchronized(_config_lock, shared=1)
1619 def GetInstancesInfoByFilter(self, filter_fn):
1620 """Get instance configuration with a filter.
1622 @type filter_fn: callable
1623 @param filter_fn: Filter function receiving instance object as parameter,
1624 returning boolean. Important: this function is called while the
1625 configuration locks is held. It must not do any complex work or call
1626 functions potentially leading to a deadlock. Ideally it doesn't call any
1627 other functions and just compares instance attributes.
1630 return dict((name, inst)
1631 for (name, inst) in self._config_data.instances.items()
1634 @locking.ssynchronized(_config_lock)
1635 def AddNode(self, node, ec_id):
1636 """Add a node to the configuration.
1638 @type node: L{objects.Node}
1639 @param node: a Node instance
1642 logging.info("Adding node %s to configuration", node.name)
1644 self._EnsureUUID(node, ec_id)
1647 node.ctime = node.mtime = time.time()
1648 self._UnlockedAddNodeToGroup(node.name, node.group)
1649 self._config_data.nodes[node.name] = node
1650 self._config_data.cluster.serial_no += 1
1653 @locking.ssynchronized(_config_lock)
1654 def RemoveNode(self, node_name):
1655 """Remove a node from the configuration.
1658 logging.info("Removing node %s from configuration", node_name)
1660 if node_name not in self._config_data.nodes:
1661 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1663 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1664 del self._config_data.nodes[node_name]
1665 self._config_data.cluster.serial_no += 1
1668 def ExpandNodeName(self, short_name):
1669 """Attempt to expand an incomplete node name.
1672 # Locking is done in L{ConfigWriter.GetNodeList}
1673 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1675 def _UnlockedGetNodeInfo(self, node_name):
1676 """Get the configuration of a node, as stored in the config.
1678 This function is for internal use, when the config lock is already
1681 @param node_name: the node name, e.g. I{node1.example.com}
1683 @rtype: L{objects.Node}
1684 @return: the node object
1687 if node_name not in self._config_data.nodes:
1690 return self._config_data.nodes[node_name]
1692 @locking.ssynchronized(_config_lock, shared=1)
1693 def GetNodeInfo(self, node_name):
1694 """Get the configuration of a node, as stored in the config.
1696 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1698 @param node_name: the node name, e.g. I{node1.example.com}
1700 @rtype: L{objects.Node}
1701 @return: the node object
1704 return self._UnlockedGetNodeInfo(node_name)
1706 @locking.ssynchronized(_config_lock, shared=1)
1707 def GetNodeInstances(self, node_name):
1708 """Get the instances of a node, as stored in the config.
1710 @param node_name: the node name, e.g. I{node1.example.com}
1712 @rtype: (list, list)
1713 @return: a tuple with two lists: the primary and the secondary instances
1718 for inst in self._config_data.instances.values():
1719 if inst.primary_node == node_name:
1720 pri.append(inst.name)
1721 if node_name in inst.secondary_nodes:
1722 sec.append(inst.name)
1725 @locking.ssynchronized(_config_lock, shared=1)
1726 def GetNodeGroupInstances(self, uuid, primary_only=False):
1727 """Get the instances of a node group.
1729 @param uuid: Node group UUID
1730 @param primary_only: Whether to only consider primary nodes
1732 @return: List of instance names in node group
1736 nodes_fn = lambda inst: [inst.primary_node]
1738 nodes_fn = lambda inst: inst.all_nodes
1740 return frozenset(inst.name
1741 for inst in self._config_data.instances.values()
1742 for node_name in nodes_fn(inst)
1743 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1745 def _UnlockedGetNodeList(self):
1746 """Return the list of nodes which are in the configuration.
1748 This function is for internal use, when the config lock is already
1754 return self._config_data.nodes.keys()
1756 @locking.ssynchronized(_config_lock, shared=1)
1757 def GetNodeList(self):
1758 """Return the list of nodes which are in the configuration.
1761 return self._UnlockedGetNodeList()
1763 def _UnlockedGetOnlineNodeList(self):
1764 """Return the list of nodes which are online.
1767 all_nodes = [self._UnlockedGetNodeInfo(node)
1768 for node in self._UnlockedGetNodeList()]
1769 return [node.name for node in all_nodes if not node.offline]
1771 @locking.ssynchronized(_config_lock, shared=1)
1772 def GetOnlineNodeList(self):
1773 """Return the list of nodes which are online.
1776 return self._UnlockedGetOnlineNodeList()
1778 @locking.ssynchronized(_config_lock, shared=1)
1779 def GetVmCapableNodeList(self):
1780 """Return the list of nodes which are not vm capable.
1783 all_nodes = [self._UnlockedGetNodeInfo(node)
1784 for node in self._UnlockedGetNodeList()]
1785 return [node.name for node in all_nodes if node.vm_capable]
1787 @locking.ssynchronized(_config_lock, shared=1)
1788 def GetNonVmCapableNodeList(self):
1789 """Return the list of nodes which are not vm capable.
1792 all_nodes = [self._UnlockedGetNodeInfo(node)
1793 for node in self._UnlockedGetNodeList()]
1794 return [node.name for node in all_nodes if not node.vm_capable]
1796 @locking.ssynchronized(_config_lock, shared=1)
1797 def GetMultiNodeInfo(self, nodes):
1798 """Get the configuration of multiple nodes.
1800 @param nodes: list of node names
1802 @return: list of tuples of (node, node_info), where node_info is
1803 what would GetNodeInfo return for the node, in the original
1807 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1809 @locking.ssynchronized(_config_lock, shared=1)
1810 def GetAllNodesInfo(self):
1811 """Get the configuration of all nodes.
1814 @return: dict of (node, node_info), where node_info is what
1815 would GetNodeInfo return for the node
1818 return self._UnlockedGetAllNodesInfo()
1820 def _UnlockedGetAllNodesInfo(self):
1821 """Gets configuration of all nodes.
1823 @note: See L{GetAllNodesInfo}
1826 return dict([(node, self._UnlockedGetNodeInfo(node))
1827 for node in self._UnlockedGetNodeList()])
1829 @locking.ssynchronized(_config_lock, shared=1)
1830 def GetNodeGroupsFromNodes(self, nodes):
1831 """Returns groups for a list of nodes.
1833 @type nodes: list of string
1834 @param nodes: List of node names
1838 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1840 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1841 """Get the number of current and maximum desired and possible candidates.
1843 @type exceptions: list
1844 @param exceptions: if passed, list of nodes that should be ignored
1846 @return: tuple of (current, desired and possible, possible)
1849 mc_now = mc_should = mc_max = 0
1850 for node in self._config_data.nodes.values():
1851 if exceptions and node.name in exceptions:
1853 if not (node.offline or node.drained) and node.master_capable:
1855 if node.master_candidate:
1857 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1858 return (mc_now, mc_should, mc_max)
1860 @locking.ssynchronized(_config_lock, shared=1)
1861 def GetMasterCandidateStats(self, exceptions=None):
1862 """Get the number of current and maximum possible candidates.
1864 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1866 @type exceptions: list
1867 @param exceptions: if passed, list of nodes that should be ignored
1869 @return: tuple of (current, max)
1872 return self._UnlockedGetMasterCandidateStats(exceptions)
1874 @locking.ssynchronized(_config_lock)
1875 def MaintainCandidatePool(self, exceptions):
1876 """Try to grow the candidate pool to the desired size.
1878 @type exceptions: list
1879 @param exceptions: if passed, list of nodes that should be ignored
1881 @return: list with the adjusted nodes (L{objects.Node} instances)
1884 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1887 node_list = self._config_data.nodes.keys()
1888 random.shuffle(node_list)
1889 for name in node_list:
1890 if mc_now >= mc_max:
1892 node = self._config_data.nodes[name]
1893 if (node.master_candidate or node.offline or node.drained or
1894 node.name in exceptions or not node.master_capable):
1896 mod_list.append(node)
1897 node.master_candidate = True
1900 if mc_now != mc_max:
1901 # this should not happen
1902 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1903 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1905 self._config_data.cluster.serial_no += 1
1910 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1911 """Add a given node to the specified group.
1914 if nodegroup_uuid not in self._config_data.nodegroups:
1915 # This can happen if a node group gets deleted between its lookup and
1916 # when we're adding the first node to it, since we don't keep a lock in
1917 # the meantime. It's ok though, as we'll fail cleanly if the node group
1918 # is not found anymore.
1919 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1920 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1921 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1923 def _UnlockedRemoveNodeFromGroup(self, node):
1924 """Remove a given node from its group.
1927 nodegroup = node.group
1928 if nodegroup not in self._config_data.nodegroups:
1929 logging.warning("Warning: node '%s' has unknown node group '%s'"
1930 " (while being removed from it)", node.name, nodegroup)
1931 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1932 if node.name not in nodegroup_obj.members:
1933 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1934 " (while being removed from it)", node.name, nodegroup)
1936 nodegroup_obj.members.remove(node.name)
1938 @locking.ssynchronized(_config_lock)
1939 def AssignGroupNodes(self, mods):
1940 """Changes the group of a number of nodes.
1942 @type mods: list of tuples; (node name, new group UUID)
1943 @param mods: Node membership modifications
1946 groups = self._config_data.nodegroups
1947 nodes = self._config_data.nodes
1951 # Try to resolve names/UUIDs first
1952 for (node_name, new_group_uuid) in mods:
1954 node = nodes[node_name]
1956 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1958 if node.group == new_group_uuid:
1959 # Node is being assigned to its current group
1960 logging.debug("Node '%s' was assigned to its current group (%s)",
1961 node_name, node.group)
1964 # Try to find current group of node
1966 old_group = groups[node.group]
1968 raise errors.ConfigurationError("Unable to find old group '%s'" %
1971 # Try to find new group for node
1973 new_group = groups[new_group_uuid]
1975 raise errors.ConfigurationError("Unable to find new group '%s'" %
1978 assert node.name in old_group.members, \
1979 ("Inconsistent configuration: node '%s' not listed in members for its"
1980 " old group '%s'" % (node.name, old_group.uuid))
1981 assert node.name not in new_group.members, \
1982 ("Inconsistent configuration: node '%s' already listed in members for"
1983 " its new group '%s'" % (node.name, new_group.uuid))
1985 resmod.append((node, old_group, new_group))
1988 for (node, old_group, new_group) in resmod:
1989 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1990 "Assigning to current group is not possible"
1992 node.group = new_group.uuid
1994 # Update members of involved groups
1995 if node.name in old_group.members:
1996 old_group.members.remove(node.name)
1997 if node.name not in new_group.members:
1998 new_group.members.append(node.name)
2000 # Update timestamps and serials (only once per node/group object)
2002 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2006 # Force ssconf update
2007 self._config_data.cluster.serial_no += 1
2011 def _BumpSerialNo(self):
2012 """Bump up the serial number of the config.
2015 self._config_data.serial_no += 1
2016 self._config_data.mtime = time.time()
2018 def _AllUUIDObjects(self):
2019 """Returns all objects with uuid attributes.
2022 return (self._config_data.instances.values() +
2023 self._config_data.nodes.values() +
2024 self._config_data.nodegroups.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
2506 if target in self._config_data.networks:
2508 for net in self._config_data.networks.values():
2509 if net.name == target:
2511 raise errors.OpPrereqError("Network '%s' not found" % target,
2514 @locking.ssynchronized(_config_lock, shared=1)
2515 def LookupNetwork(self, target):
2516 """Lookup a network's UUID.
2518 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2520 @type target: string
2521 @param target: network name or UUID
2523 @return: network UUID
2526 return self._UnlockedLookupNetwork(target)
2528 @locking.ssynchronized(_config_lock)
2529 def RemoveNetwork(self, network_uuid):
2530 """Remove a network from the configuration.
2532 @type network_uuid: string
2533 @param network_uuid: the UUID of the network to remove
2536 logging.info("Removing network %s from configuration", network_uuid)
2538 if network_uuid not in self._config_data.networks:
2539 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2541 del self._config_data.networks[network_uuid]
2542 self._config_data.cluster.serial_no += 1
2545 def _UnlockedGetGroupNetParams(self, net_uuid, node):
2546 """Get the netparams (mode, link) of a network.
2548 Get a network's netparams for a given node.
2550 @type net_uuid: string
2551 @param net_uuid: network uuid
2553 @param node: node name
2554 @rtype: dict or None
2558 node_info = self._UnlockedGetNodeInfo(node)
2559 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2560 netparams = nodegroup_info.networks.get(net_uuid, None)
2564 @locking.ssynchronized(_config_lock, shared=1)
2565 def GetGroupNetParams(self, net_uuid, node):
2566 """Locking wrapper of _UnlockedGetGroupNetParams()
2569 return self._UnlockedGetGroupNetParams(net_uuid, node)
2571 @locking.ssynchronized(_config_lock, shared=1)
2572 def CheckIPInNodeGroup(self, ip, node):
2573 """Check IP uniqueness in nodegroup.
2575 Check networks that are connected in the node's node group
2576 if ip is contained in any of them. Used when creating/adding
2577 a NIC to ensure uniqueness among nodegroups.
2580 @param ip: ip address
2582 @param node: node name
2583 @rtype: (string, dict) or (None, None)
2584 @return: (network name, netparams)
2589 node_info = self._UnlockedGetNodeInfo(node)
2590 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2591 for net_uuid in nodegroup_info.networks.keys():
2592 net_info = self._UnlockedGetNetwork(net_uuid)
2593 pool = network.AddressPool(net_info)
2594 if pool.Contains(ip):
2595 return (net_info.name, nodegroup_info.networks[net_uuid])