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
43 from ganeti import errors
44 from ganeti import locking
45 from ganeti import utils
46 from ganeti import constants
47 from ganeti import rpc
48 from ganeti import objects
49 from ganeti import serializer
50 from ganeti import uidpool
51 from ganeti import netutils
52 from ganeti import runtime
53 from ganeti import pathutils
54 from ganeti import network
57 _config_lock = locking.SharedLock("ConfigWriter")
59 # job id used for resource management at config upgrade time
60 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
63 def _ValidateConfig(data):
64 """Verifies that a configuration objects looks valid.
66 This only verifies the version of the configuration.
68 @raise errors.ConfigurationError: if the version differs from what
72 if data.version != constants.CONFIG_VERSION:
73 raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
76 class TemporaryReservationManager:
77 """A temporary resource reservation manager.
79 This is used to reserve resources in a job, before using them, making sure
80 other jobs cannot get them in the meantime.
84 self._ec_reserved = {}
86 def Reserved(self, resource):
87 for holder_reserved in self._ec_reserved.values():
88 if resource in holder_reserved:
92 def Reserve(self, ec_id, resource):
93 if self.Reserved(resource):
94 raise errors.ReservationError("Duplicate reservation for resource '%s'"
96 if ec_id not in self._ec_reserved:
97 self._ec_reserved[ec_id] = set([resource])
99 self._ec_reserved[ec_id].add(resource)
101 def DropECReservations(self, ec_id):
102 if ec_id in self._ec_reserved:
103 del self._ec_reserved[ec_id]
105 def GetReserved(self):
107 for holder_reserved in self._ec_reserved.values():
108 all_reserved.update(holder_reserved)
111 def GetECReserved(self, ec_id):
113 if ec_id in self._ec_reserved:
114 ec_reserved.update(self._ec_reserved[ec_id])
118 def Generate(self, existing, generate_one_fn, ec_id):
119 """Generate a new resource of this type
122 assert callable(generate_one_fn)
124 all_elems = self.GetReserved()
125 all_elems.update(existing)
128 new_resource = generate_one_fn()
129 if new_resource is not None and new_resource not in all_elems:
132 raise errors.ConfigurationError("Not able generate new resource"
133 " (last tried: %s)" % new_resource)
134 self.Reserve(ec_id, new_resource)
138 def _MatchNameComponentIgnoreCase(short_name, names):
139 """Wrapper around L{utils.text.MatchNameComponent}.
142 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
145 def _CheckInstanceDiskIvNames(disks):
146 """Checks if instance's disks' C{iv_name} attributes are in order.
148 @type disks: list of L{objects.Disk}
149 @param disks: List of disks
150 @rtype: list of tuples; (int, string, string)
151 @return: List of wrongly named disks, each tuple contains disk index,
152 expected and actual name
157 for (idx, disk) in enumerate(disks):
158 exp_iv_name = "disk/%s" % idx
159 if disk.iv_name != exp_iv_name:
160 result.append((idx, exp_iv_name, disk.iv_name))
166 """The interface to the cluster configuration.
168 @ivar _temporary_lvs: reservation manager for temporary LVs
169 @ivar _all_rms: a list of all temporary reservation managers
172 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
173 accept_foreign=False):
175 self._lock = _config_lock
176 self._config_data = None
177 self._offline = offline
179 self._cfg_file = pathutils.CLUSTER_CONF_FILE
181 self._cfg_file = cfg_file
182 self._getents = _getents
183 self._temporary_ids = TemporaryReservationManager()
184 self._temporary_drbds = {}
185 self._temporary_macs = TemporaryReservationManager()
186 self._temporary_secrets = TemporaryReservationManager()
187 self._temporary_lvs = TemporaryReservationManager()
188 self._temporary_ips = TemporaryReservationManager()
189 self._all_rms = [self._temporary_ids, self._temporary_macs,
190 self._temporary_secrets, self._temporary_lvs,
192 # Note: in order to prevent errors when resolving our name in
193 # _DistributeConfig, we compute it here once and reuse it; it's
194 # better to raise an error before starting to modify the config
195 # file than after it was modified
196 self._my_hostname = netutils.Hostname.GetSysName()
197 self._last_cluster_serial = -1
200 self._OpenConfig(accept_foreign)
202 def _GetRpc(self, address_list):
203 """Returns RPC runner for configuration.
206 return rpc.ConfigRunner(self._context, address_list)
208 def SetContext(self, context):
209 """Sets Ganeti context.
212 self._context = context
214 # this method needs to be static, so that we can call it on the class
217 """Check if the cluster is configured.
220 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
222 def _GenerateOneMAC(self):
223 """Generate one mac address
226 prefix = self._config_data.cluster.mac_prefix
227 byte1 = random.randrange(0, 256)
228 byte2 = random.randrange(0, 256)
229 byte3 = random.randrange(0, 256)
230 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
233 @locking.ssynchronized(_config_lock, shared=1)
234 def GetNdParams(self, node):
235 """Get the node params populated with cluster defaults.
237 @type node: L{objects.Node}
238 @param node: The node we want to know the params for
239 @return: A dict with the filled in node params
242 nodegroup = self._UnlockedGetNodeGroup(node.group)
243 return self._config_data.cluster.FillND(node, nodegroup)
245 @locking.ssynchronized(_config_lock, shared=1)
246 def GetInstanceDiskParams(self, instance):
247 """Get the disk params populated with inherit chain.
249 @type instance: L{objects.Instance}
250 @param instance: The instance we want to know the params for
251 @return: A dict with the filled in disk params
254 node = self._UnlockedGetNodeInfo(instance.primary_node)
255 nodegroup = self._UnlockedGetNodeGroup(node.group)
256 return self._UnlockedGetGroupDiskParams(nodegroup)
258 @locking.ssynchronized(_config_lock, shared=1)
259 def GetGroupDiskParams(self, group):
260 """Get the disk params populated with inherit chain.
262 @type group: L{objects.NodeGroup}
263 @param group: The group we want to know the params for
264 @return: A dict with the filled in disk params
267 return self._UnlockedGetGroupDiskParams(group)
269 def _UnlockedGetGroupDiskParams(self, group):
270 """Get the disk params populated with inherit chain down to node-group.
272 @type group: L{objects.NodeGroup}
273 @param group: The group we want to know the params for
274 @return: A dict with the filled in disk params
277 return self._config_data.cluster.SimpleFillDP(group.diskparams)
279 @locking.ssynchronized(_config_lock, shared=1)
280 def GenerateMAC(self, ec_id):
281 """Generate a MAC for an instance.
283 This should check the current instances for duplicates.
286 existing = self._AllMACs()
287 return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
289 @locking.ssynchronized(_config_lock, shared=1)
290 def ReserveMAC(self, mac, ec_id):
291 """Reserve a MAC for an instance.
293 This only checks instances managed by this cluster, it does not
294 check for potential collisions elsewhere.
297 all_macs = self._AllMACs()
299 raise errors.ReservationError("mac already in use")
301 self._temporary_macs.Reserve(ec_id, mac)
303 def _UnlockedCommitTemporaryIps(self, ec_id):
304 """Commit all reserved IP address to their respective pools
307 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
308 self._UnlockedCommitIp(action, net_uuid, address)
310 def _UnlockedCommitIp(self, action, net_uuid, address):
311 """Commit a reserved IP address to an IP pool.
313 The IP address is taken from the network's IP pool and marked as reserved.
316 nobj = self._UnlockedGetNetwork(net_uuid)
317 pool = network.AddressPool(nobj)
318 if action == 'reserve':
319 pool.Reserve(address)
320 elif action == 'release':
321 pool.Release(address)
323 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
324 """Give a specific IP address back to an IP pool.
326 The IP address is returned to the IP pool designated by pool_id and marked
330 nobj = self._UnlockedGetNetwork(net_uuid)
331 pool = network.AddressPool(nobj)
332 self._temporary_ips.Reserve(ec_id, ('release', address, net_uuid))
334 @locking.ssynchronized(_config_lock, shared=1)
335 def ReleaseIp(self, network, address, ec_id):
336 """Give a specified IP address back to an IP pool.
338 This is just a wrapper around _UnlockedReleaseIp.
341 net_uuid = self._UnlockedLookupNetwork(network)
343 self._UnlockedReleaseIp(net_uuid, address, ec_id)
345 @locking.ssynchronized(_config_lock, shared=1)
346 def GenerateIp(self, net, ec_id):
347 """Find a free IPv4 address for an instance.
350 net_uuid = self._UnlockedLookupNetwork(net)
351 nobj = self._UnlockedGetNetwork(net_uuid)
352 pool = network.AddressPool(nobj)
353 gen_free = pool.GenerateFree()
358 except StopIteration:
359 raise errors.ReservationError("Cannot generate IP. Network is full")
360 return ("reserve", ip, net_uuid)
362 _ ,address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
365 def _UnlockedReserveIp(self, net_uuid, address, ec_id):
366 """Reserve a given IPv4 address for use by an instance.
369 nobj = self._UnlockedGetNetwork(net_uuid)
370 pool = network.AddressPool(nobj)
372 isreserved = pool.IsReserved(address)
373 except errors.AddressPoolError:
374 raise errors.ReservationError("IP address not in network")
376 raise errors.ReservationError("IP address already in use")
378 return self._temporary_ips.Reserve(ec_id, ('reserve', address, net_uuid))
381 @locking.ssynchronized(_config_lock, shared=1)
382 def ReserveIp(self, net, address, ec_id):
383 """Reserve a given IPv4 address for use by an instance.
386 net_uuid = self._UnlockedLookupNetwork(net)
388 return self._UnlockedReserveIp(net_uuid, address, ec_id)
390 @locking.ssynchronized(_config_lock, shared=1)
391 def ReserveLV(self, lv_name, ec_id):
392 """Reserve an VG/LV pair for an instance.
394 @type lv_name: string
395 @param lv_name: the logical volume name to reserve
398 all_lvs = self._AllLVs()
399 if lv_name in all_lvs:
400 raise errors.ReservationError("LV already in use")
402 self._temporary_lvs.Reserve(ec_id, lv_name)
404 @locking.ssynchronized(_config_lock, shared=1)
405 def GenerateDRBDSecret(self, ec_id):
406 """Generate a DRBD secret.
408 This checks the current disks for duplicates.
411 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
412 utils.GenerateSecret,
416 """Compute the list of all LVs.
420 for instance in self._config_data.instances.values():
421 node_data = instance.MapLVsByNode()
422 for lv_list in node_data.values():
423 lvnames.update(lv_list)
426 def _AllIDs(self, include_temporary):
427 """Compute the list of all UUIDs and names we have.
429 @type include_temporary: boolean
430 @param include_temporary: whether to include the _temporary_ids set
432 @return: a set of IDs
436 if include_temporary:
437 existing.update(self._temporary_ids.GetReserved())
438 existing.update(self._AllLVs())
439 existing.update(self._config_data.instances.keys())
440 existing.update(self._config_data.nodes.keys())
441 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
444 def _GenerateUniqueID(self, ec_id):
445 """Generate an unique UUID.
447 This checks the current node, instances and disk names for
451 @return: the unique id
454 existing = self._AllIDs(include_temporary=False)
455 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
457 @locking.ssynchronized(_config_lock, shared=1)
458 def GenerateUniqueID(self, ec_id):
459 """Generate an unique ID.
461 This is just a wrapper over the unlocked version.
464 @param ec_id: unique id for the job to reserve the id to
467 return self._GenerateUniqueID(ec_id)
470 """Return all MACs present in the config.
473 @return: the list of all MACs
477 for instance in self._config_data.instances.values():
478 for nic in instance.nics:
479 result.append(nic.mac)
483 def _AllDRBDSecrets(self):
484 """Return all DRBD secrets present in the config.
487 @return: the list of all DRBD secrets
490 def helper(disk, result):
491 """Recursively gather secrets from this disk."""
492 if disk.dev_type == constants.DT_DRBD8:
493 result.append(disk.logical_id[5])
495 for child in disk.children:
496 helper(child, result)
499 for instance in self._config_data.instances.values():
500 for disk in instance.disks:
505 def _CheckDiskIDs(self, disk, l_ids, p_ids):
506 """Compute duplicate disk IDs
508 @type disk: L{objects.Disk}
509 @param disk: the disk at which to start searching
511 @param l_ids: list of current logical ids
513 @param p_ids: list of current physical ids
515 @return: a list of error messages
519 if disk.logical_id is not None:
520 if disk.logical_id in l_ids:
521 result.append("duplicate logical id %s" % str(disk.logical_id))
523 l_ids.append(disk.logical_id)
524 if disk.physical_id is not None:
525 if disk.physical_id in p_ids:
526 result.append("duplicate physical id %s" % str(disk.physical_id))
528 p_ids.append(disk.physical_id)
531 for child in disk.children:
532 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
535 def _UnlockedVerifyConfig(self):
539 @return: a list of error messages; a non-empty list signifies
543 # pylint: disable=R0914
547 data = self._config_data
548 cluster = data.cluster
552 # global cluster checks
553 if not cluster.enabled_hypervisors:
554 result.append("enabled hypervisors list doesn't have any entries")
555 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
557 result.append("enabled hypervisors contains invalid entries: %s" %
559 missing_hvp = (set(cluster.enabled_hypervisors) -
560 set(cluster.hvparams.keys()))
562 result.append("hypervisor parameters missing for the enabled"
563 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
565 if cluster.master_node not in data.nodes:
566 result.append("cluster has invalid primary node '%s'" %
569 def _helper(owner, attr, value, template):
571 utils.ForceDictType(value, template)
572 except errors.GenericError, err:
573 result.append("%s has invalid %s: %s" % (owner, attr, err))
575 def _helper_nic(owner, params):
577 objects.NIC.CheckParameterSyntax(params)
578 except errors.ConfigurationError, err:
579 result.append("%s has invalid nicparams: %s" % (owner, err))
581 def _helper_ipolicy(owner, params, check_std):
583 objects.InstancePolicy.CheckParameterSyntax(params, check_std)
584 except errors.ConfigurationError, err:
585 result.append("%s has invalid instance policy: %s" % (owner, err))
587 def _helper_ispecs(owner, params):
588 for key, value in params.items():
589 if key in constants.IPOLICY_ISPECS:
590 fullkey = "ipolicy/" + key
591 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
593 # FIXME: assuming list type
594 if key in constants.IPOLICY_PARAMETERS:
598 if not isinstance(value, exp_type):
599 result.append("%s has invalid instance policy: for %s,"
600 " expecting %s, got %s" %
601 (owner, key, exp_type.__name__, type(value)))
603 # check cluster parameters
604 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
605 constants.BES_PARAMETER_TYPES)
606 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
607 constants.NICS_PARAMETER_TYPES)
608 _helper_nic("cluster", cluster.SimpleFillNIC({}))
609 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
610 constants.NDS_PARAMETER_TYPES)
611 _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
612 _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
614 # per-instance checks
615 for instance_name in data.instances:
616 instance = data.instances[instance_name]
617 if instance.name != instance_name:
618 result.append("instance '%s' is indexed by wrong name '%s'" %
619 (instance.name, instance_name))
620 if instance.primary_node not in data.nodes:
621 result.append("instance '%s' has invalid primary node '%s'" %
622 (instance_name, instance.primary_node))
623 for snode in instance.secondary_nodes:
624 if snode not in data.nodes:
625 result.append("instance '%s' has invalid secondary node '%s'" %
626 (instance_name, snode))
627 for idx, nic in enumerate(instance.nics):
628 if nic.mac in seen_macs:
629 result.append("instance '%s' has NIC %d mac %s duplicate" %
630 (instance_name, idx, nic.mac))
632 seen_macs.append(nic.mac)
634 filled = cluster.SimpleFillNIC(nic.nicparams)
635 owner = "instance %s nic %d" % (instance.name, idx)
636 _helper(owner, "nicparams",
637 filled, constants.NICS_PARAMETER_TYPES)
638 _helper_nic(owner, filled)
641 if instance.beparams:
642 _helper("instance %s" % instance.name, "beparams",
643 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
645 # gather the drbd ports for duplicate checks
646 for (idx, dsk) in enumerate(instance.disks):
647 if dsk.dev_type in constants.LDS_DRBD:
648 tcp_port = dsk.logical_id[2]
649 if tcp_port not in ports:
651 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
652 # gather network port reservation
653 net_port = getattr(instance, "network_port", None)
654 if net_port is not None:
655 if net_port not in ports:
657 ports[net_port].append((instance.name, "network port"))
659 # instance disk verify
660 for idx, disk in enumerate(instance.disks):
661 result.extend(["instance '%s' disk %d error: %s" %
662 (instance.name, idx, msg) for msg in disk.Verify()])
663 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
665 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
667 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
668 (idx, exp_name, actual_name))
669 for (idx, exp_name, actual_name) in wrong_names)
671 result.append("Instance '%s' has wrongly named disks: %s" %
672 (instance.name, tmp))
674 # cluster-wide pool of free ports
675 for free_port in cluster.tcpudp_port_pool:
676 if free_port not in ports:
677 ports[free_port] = []
678 ports[free_port].append(("cluster", "port marked as free"))
680 # compute tcp/udp duplicate ports
686 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
687 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
689 # highest used tcp port check
691 if keys[-1] > cluster.highest_used_port:
692 result.append("Highest used port mismatch, saved %s, computed %s" %
693 (cluster.highest_used_port, keys[-1]))
695 if not data.nodes[cluster.master_node].master_candidate:
696 result.append("Master node is not a master candidate")
698 # master candidate checks
699 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
701 result.append("Not enough master candidates: actual %d, target %d" %
705 for node_name, node in data.nodes.items():
706 if node.name != node_name:
707 result.append("Node '%s' is indexed by wrong name '%s'" %
708 (node.name, node_name))
709 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
710 result.append("Node %s state is invalid: master_candidate=%s,"
711 " drain=%s, offline=%s" %
712 (node.name, node.master_candidate, node.drained,
714 if node.group not in data.nodegroups:
715 result.append("Node '%s' has invalid group '%s'" %
716 (node.name, node.group))
718 _helper("node %s" % node.name, "ndparams",
719 cluster.FillND(node, data.nodegroups[node.group]),
720 constants.NDS_PARAMETER_TYPES)
723 nodegroups_names = set()
724 for nodegroup_uuid in data.nodegroups:
725 nodegroup = data.nodegroups[nodegroup_uuid]
726 if nodegroup.uuid != nodegroup_uuid:
727 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
728 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
729 if utils.UUID_RE.match(nodegroup.name.lower()):
730 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
731 (nodegroup.name, nodegroup.uuid))
732 if nodegroup.name in nodegroups_names:
733 result.append("duplicate node group name '%s'" % nodegroup.name)
735 nodegroups_names.add(nodegroup.name)
736 group_name = "group %s" % nodegroup.name
737 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
739 _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
740 if nodegroup.ndparams:
741 _helper(group_name, "ndparams",
742 cluster.SimpleFillND(nodegroup.ndparams),
743 constants.NDS_PARAMETER_TYPES)
746 _, duplicates = self._UnlockedComputeDRBDMap()
747 for node, minor, instance_a, instance_b in duplicates:
748 result.append("DRBD minor %d on node %s is assigned twice to instances"
749 " %s and %s" % (minor, node, instance_a, instance_b))
752 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
755 def _AddIpAddress(ip, name):
756 ips.setdefault(ip, []).append(name)
758 _AddIpAddress(cluster.master_ip, "cluster_ip")
760 for node in data.nodes.values():
761 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
762 if node.secondary_ip != node.primary_ip:
763 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
765 for instance in data.instances.values():
766 for idx, nic in enumerate(instance.nics):
770 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
771 nic_mode = nicparams[constants.NIC_MODE]
772 nic_link = nicparams[constants.NIC_LINK]
774 if nic_mode == constants.NIC_MODE_BRIDGED:
775 link = "bridge:%s" % nic_link
776 elif nic_mode == constants.NIC_MODE_ROUTED:
777 link = "route:%s" % nic_link
779 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
781 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
782 "instance:%s/nic:%d" % (instance.name, idx))
784 for ip, owners in ips.items():
786 result.append("IP address %s is used by multiple owners: %s" %
787 (ip, utils.CommaJoin(owners)))
791 @locking.ssynchronized(_config_lock, shared=1)
792 def VerifyConfig(self):
795 This is just a wrapper over L{_UnlockedVerifyConfig}.
798 @return: a list of error messages; a non-empty list signifies
802 return self._UnlockedVerifyConfig()
804 def _UnlockedSetDiskID(self, disk, node_name):
805 """Convert the unique ID to the ID needed on the target nodes.
807 This is used only for drbd, which needs ip/port configuration.
809 The routine descends down and updates its children also, because
810 this helps when the only the top device is passed to the remote
813 This function is for internal use, when the config lock is already held.
817 for child in disk.children:
818 self._UnlockedSetDiskID(child, node_name)
820 if disk.logical_id is None and disk.physical_id is not None:
822 if disk.dev_type == constants.LD_DRBD8:
823 pnode, snode, port, pminor, sminor, secret = disk.logical_id
824 if node_name not in (pnode, snode):
825 raise errors.ConfigurationError("DRBD device not knowing node %s" %
827 pnode_info = self._UnlockedGetNodeInfo(pnode)
828 snode_info = self._UnlockedGetNodeInfo(snode)
829 if pnode_info is None or snode_info is None:
830 raise errors.ConfigurationError("Can't find primary or secondary node"
831 " for %s" % str(disk))
832 p_data = (pnode_info.secondary_ip, port)
833 s_data = (snode_info.secondary_ip, port)
834 if pnode == node_name:
835 disk.physical_id = p_data + s_data + (pminor, secret)
836 else: # it must be secondary, we tested above
837 disk.physical_id = s_data + p_data + (sminor, secret)
839 disk.physical_id = disk.logical_id
842 @locking.ssynchronized(_config_lock)
843 def SetDiskID(self, disk, node_name):
844 """Convert the unique ID to the ID needed on the target nodes.
846 This is used only for drbd, which needs ip/port configuration.
848 The routine descends down and updates its children also, because
849 this helps when the only the top device is passed to the remote
853 return self._UnlockedSetDiskID(disk, node_name)
855 @locking.ssynchronized(_config_lock)
856 def AddTcpUdpPort(self, port):
857 """Adds a new port to the available port pool.
859 @warning: this method does not "flush" the configuration (via
860 L{_WriteConfig}); callers should do that themselves once the
861 configuration is stable
864 if not isinstance(port, int):
865 raise errors.ProgrammerError("Invalid type passed for port")
867 self._config_data.cluster.tcpudp_port_pool.add(port)
869 @locking.ssynchronized(_config_lock, shared=1)
870 def GetPortList(self):
871 """Returns a copy of the current port list.
874 return self._config_data.cluster.tcpudp_port_pool.copy()
876 @locking.ssynchronized(_config_lock)
877 def AllocatePort(self):
880 The port will be taken from the available port pool or from the
881 default port range (and in this case we increase
885 # If there are TCP/IP ports configured, we use them first.
886 if self._config_data.cluster.tcpudp_port_pool:
887 port = self._config_data.cluster.tcpudp_port_pool.pop()
889 port = self._config_data.cluster.highest_used_port + 1
890 if port >= constants.LAST_DRBD_PORT:
891 raise errors.ConfigurationError("The highest used port is greater"
892 " than %s. Aborting." %
893 constants.LAST_DRBD_PORT)
894 self._config_data.cluster.highest_used_port = port
899 def _UnlockedComputeDRBDMap(self):
900 """Compute the used DRBD minor/nodes.
903 @return: dictionary of node_name: dict of minor: instance_name;
904 the returned dict will have all the nodes in it (even if with
905 an empty list), and a list of duplicates; if the duplicates
906 list is not empty, the configuration is corrupted and its caller
907 should raise an exception
910 def _AppendUsedPorts(instance_name, disk, used):
912 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
913 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
914 for node, port in ((node_a, minor_a), (node_b, minor_b)):
915 assert node in used, ("Node '%s' of instance '%s' not found"
916 " in node list" % (node, instance_name))
917 if port in used[node]:
918 duplicates.append((node, port, instance_name, used[node][port]))
920 used[node][port] = instance_name
922 for child in disk.children:
923 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
927 my_dict = dict((node, {}) for node in self._config_data.nodes)
928 for instance in self._config_data.instances.itervalues():
929 for disk in instance.disks:
930 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
931 for (node, minor), instance in self._temporary_drbds.iteritems():
932 if minor in my_dict[node] and my_dict[node][minor] != instance:
933 duplicates.append((node, minor, instance, my_dict[node][minor]))
935 my_dict[node][minor] = instance
936 return my_dict, duplicates
938 @locking.ssynchronized(_config_lock)
939 def ComputeDRBDMap(self):
940 """Compute the used DRBD minor/nodes.
942 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
944 @return: dictionary of node_name: dict of minor: instance_name;
945 the returned dict will have all the nodes in it (even if with
949 d_map, duplicates = self._UnlockedComputeDRBDMap()
951 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
955 @locking.ssynchronized(_config_lock)
956 def AllocateDRBDMinor(self, nodes, instance):
957 """Allocate a drbd minor.
959 The free minor will be automatically computed from the existing
960 devices. A node can be given multiple times in order to allocate
961 multiple minors. The result is the list of minors, in the same
962 order as the passed nodes.
964 @type instance: string
965 @param instance: the instance for which we allocate minors
968 assert isinstance(instance, basestring), \
969 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
971 d_map, duplicates = self._UnlockedComputeDRBDMap()
973 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
979 # no minors used, we can start at 0
982 self._temporary_drbds[(nname, 0)] = instance
986 ffree = utils.FirstFree(keys)
988 # return the next minor
989 # TODO: implement high-limit check
993 # double-check minor against current instances
994 assert minor not in d_map[nname], \
995 ("Attempt to reuse allocated DRBD minor %d on node %s,"
996 " already allocated to instance %s" %
997 (minor, nname, d_map[nname][minor]))
998 ndata[minor] = instance
999 # double-check minor against reservation
1000 r_key = (nname, minor)
1001 assert r_key not in self._temporary_drbds, \
1002 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1003 " reserved for instance %s" %
1004 (minor, nname, self._temporary_drbds[r_key]))
1005 self._temporary_drbds[r_key] = instance
1006 result.append(minor)
1007 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1011 def _UnlockedReleaseDRBDMinors(self, instance):
1012 """Release temporary drbd minors allocated for a given instance.
1014 @type instance: string
1015 @param instance: the instance for which temporary minors should be
1019 assert isinstance(instance, basestring), \
1020 "Invalid argument passed to ReleaseDRBDMinors"
1021 for key, name in self._temporary_drbds.items():
1022 if name == instance:
1023 del self._temporary_drbds[key]
1025 @locking.ssynchronized(_config_lock)
1026 def ReleaseDRBDMinors(self, instance):
1027 """Release temporary drbd minors allocated for a given instance.
1029 This should be called on the error paths, on the success paths
1030 it's automatically called by the ConfigWriter add and update
1033 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1035 @type instance: string
1036 @param instance: the instance for which temporary minors should be
1040 self._UnlockedReleaseDRBDMinors(instance)
1042 @locking.ssynchronized(_config_lock, shared=1)
1043 def GetConfigVersion(self):
1044 """Get the configuration version.
1046 @return: Config version
1049 return self._config_data.version
1051 @locking.ssynchronized(_config_lock, shared=1)
1052 def GetClusterName(self):
1053 """Get cluster name.
1055 @return: Cluster name
1058 return self._config_data.cluster.cluster_name
1060 @locking.ssynchronized(_config_lock, shared=1)
1061 def GetMasterNode(self):
1062 """Get the hostname of the master node for this cluster.
1064 @return: Master hostname
1067 return self._config_data.cluster.master_node
1069 @locking.ssynchronized(_config_lock, shared=1)
1070 def GetMasterIP(self):
1071 """Get the IP of the master node for this cluster.
1076 return self._config_data.cluster.master_ip
1078 @locking.ssynchronized(_config_lock, shared=1)
1079 def GetMasterNetdev(self):
1080 """Get the master network device for this cluster.
1083 return self._config_data.cluster.master_netdev
1085 @locking.ssynchronized(_config_lock, shared=1)
1086 def GetMasterNetmask(self):
1087 """Get the netmask of the master node for this cluster.
1090 return self._config_data.cluster.master_netmask
1092 @locking.ssynchronized(_config_lock, shared=1)
1093 def GetUseExternalMipScript(self):
1094 """Get flag representing whether to use the external master IP setup script.
1097 return self._config_data.cluster.use_external_mip_script
1099 @locking.ssynchronized(_config_lock, shared=1)
1100 def GetFileStorageDir(self):
1101 """Get the file storage dir for this cluster.
1104 return self._config_data.cluster.file_storage_dir
1106 @locking.ssynchronized(_config_lock, shared=1)
1107 def GetSharedFileStorageDir(self):
1108 """Get the shared file storage dir for this cluster.
1111 return self._config_data.cluster.shared_file_storage_dir
1113 @locking.ssynchronized(_config_lock, shared=1)
1114 def GetHypervisorType(self):
1115 """Get the hypervisor type for this cluster.
1118 return self._config_data.cluster.enabled_hypervisors[0]
1120 @locking.ssynchronized(_config_lock, shared=1)
1121 def GetHostKey(self):
1122 """Return the rsa hostkey from the config.
1125 @return: the rsa hostkey
1128 return self._config_data.cluster.rsahostkeypub
1130 @locking.ssynchronized(_config_lock, shared=1)
1131 def GetDefaultIAllocator(self):
1132 """Get the default instance allocator for this cluster.
1135 return self._config_data.cluster.default_iallocator
1137 @locking.ssynchronized(_config_lock, shared=1)
1138 def GetPrimaryIPFamily(self):
1139 """Get cluster primary ip family.
1141 @return: primary ip family
1144 return self._config_data.cluster.primary_ip_family
1146 @locking.ssynchronized(_config_lock, shared=1)
1147 def GetMasterNetworkParameters(self):
1148 """Get network parameters of the master node.
1150 @rtype: L{object.MasterNetworkParameters}
1151 @return: network parameters of the master node
1154 cluster = self._config_data.cluster
1155 result = objects.MasterNetworkParameters(
1156 name=cluster.master_node, ip=cluster.master_ip,
1157 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1158 ip_family=cluster.primary_ip_family)
1162 @locking.ssynchronized(_config_lock)
1163 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1164 """Add a node group to the configuration.
1166 This method calls group.UpgradeConfig() to fill any missing attributes
1167 according to their default values.
1169 @type group: L{objects.NodeGroup}
1170 @param group: the NodeGroup object to add
1172 @param ec_id: unique id for the job to use when creating a missing UUID
1173 @type check_uuid: bool
1174 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1175 it does, ensure that it does not exist in the
1176 configuration already
1179 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1182 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1183 """Add a node group to the configuration.
1186 logging.info("Adding node group %s to configuration", group.name)
1188 # Some code might need to add a node group with a pre-populated UUID
1189 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1190 # the "does this UUID" exist already check.
1192 self._EnsureUUID(group, ec_id)
1195 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1196 except errors.OpPrereqError:
1199 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1200 " node group (UUID: %s)" %
1201 (group.name, existing_uuid),
1202 errors.ECODE_EXISTS)
1205 group.ctime = group.mtime = time.time()
1206 group.UpgradeConfig()
1208 self._config_data.nodegroups[group.uuid] = group
1209 self._config_data.cluster.serial_no += 1
1211 @locking.ssynchronized(_config_lock)
1212 def RemoveNodeGroup(self, group_uuid):
1213 """Remove a node group from the configuration.
1215 @type group_uuid: string
1216 @param group_uuid: the UUID of the node group to remove
1219 logging.info("Removing node group %s from configuration", group_uuid)
1221 if group_uuid not in self._config_data.nodegroups:
1222 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1224 assert len(self._config_data.nodegroups) != 1, \
1225 "Group '%s' is the only group, cannot be removed" % group_uuid
1227 del self._config_data.nodegroups[group_uuid]
1228 self._config_data.cluster.serial_no += 1
1231 def _UnlockedLookupNodeGroup(self, target):
1232 """Lookup a node group's UUID.
1234 @type target: string or None
1235 @param target: group name or UUID or None to look for the default
1237 @return: nodegroup UUID
1238 @raises errors.OpPrereqError: when the target group cannot be found
1242 if len(self._config_data.nodegroups) != 1:
1243 raise errors.OpPrereqError("More than one node group exists. Target"
1244 " group must be specified explicitly.")
1246 return self._config_data.nodegroups.keys()[0]
1247 if target in self._config_data.nodegroups:
1249 for nodegroup in self._config_data.nodegroups.values():
1250 if nodegroup.name == target:
1251 return nodegroup.uuid
1252 raise errors.OpPrereqError("Node group '%s' not found" % target,
1255 @locking.ssynchronized(_config_lock, shared=1)
1256 def LookupNodeGroup(self, target):
1257 """Lookup a node group's UUID.
1259 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1261 @type target: string or None
1262 @param target: group name or UUID or None to look for the default
1264 @return: nodegroup UUID
1267 return self._UnlockedLookupNodeGroup(target)
1269 def _UnlockedGetNodeGroup(self, uuid):
1270 """Lookup a node group.
1273 @param uuid: group UUID
1274 @rtype: L{objects.NodeGroup} or None
1275 @return: nodegroup object, or None if not found
1278 if uuid not in self._config_data.nodegroups:
1281 return self._config_data.nodegroups[uuid]
1283 @locking.ssynchronized(_config_lock, shared=1)
1284 def GetNodeGroup(self, uuid):
1285 """Lookup a node group.
1288 @param uuid: group UUID
1289 @rtype: L{objects.NodeGroup} or None
1290 @return: nodegroup object, or None if not found
1293 return self._UnlockedGetNodeGroup(uuid)
1295 @locking.ssynchronized(_config_lock, shared=1)
1296 def GetAllNodeGroupsInfo(self):
1297 """Get the configuration of all node groups.
1300 return dict(self._config_data.nodegroups)
1302 @locking.ssynchronized(_config_lock, shared=1)
1303 def GetNodeGroupList(self):
1304 """Get a list of node groups.
1307 return self._config_data.nodegroups.keys()
1309 @locking.ssynchronized(_config_lock, shared=1)
1310 def GetNodeGroupMembersByNodes(self, nodes):
1311 """Get nodes which are member in the same nodegroups as the given nodes.
1314 ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1315 return frozenset(member_name
1316 for node_name in nodes
1318 self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1320 @locking.ssynchronized(_config_lock, shared=1)
1321 def GetMultiNodeGroupInfo(self, group_uuids):
1322 """Get the configuration of multiple node groups.
1324 @param group_uuids: List of node group UUIDs
1326 @return: List of tuples of (group_uuid, group_info)
1329 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1331 @locking.ssynchronized(_config_lock)
1332 def AddInstance(self, instance, ec_id):
1333 """Add an instance to the config.
1335 This should be used after creating a new instance.
1337 @type instance: L{objects.Instance}
1338 @param instance: the instance object
1341 if not isinstance(instance, objects.Instance):
1342 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1344 if instance.disk_template != constants.DT_DISKLESS:
1345 all_lvs = instance.MapLVsByNode()
1346 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1348 all_macs = self._AllMACs()
1349 for nic in instance.nics:
1350 if nic.mac in all_macs:
1351 raise errors.ConfigurationError("Cannot add instance %s:"
1352 " MAC address '%s' already in use." %
1353 (instance.name, nic.mac))
1355 self._EnsureUUID(instance, ec_id)
1357 instance.serial_no = 1
1358 instance.ctime = instance.mtime = time.time()
1359 self._config_data.instances[instance.name] = instance
1360 self._config_data.cluster.serial_no += 1
1361 self._UnlockedReleaseDRBDMinors(instance.name)
1362 self._UnlockedCommitTemporaryIps(ec_id)
1365 def _EnsureUUID(self, item, ec_id):
1366 """Ensures a given object has a valid UUID.
1368 @param item: the instance or node to be checked
1369 @param ec_id: the execution context id for the uuid reservation
1373 item.uuid = self._GenerateUniqueID(ec_id)
1374 elif item.uuid in self._AllIDs(include_temporary=True):
1375 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1376 " in use" % (item.name, item.uuid))
1378 def _SetInstanceStatus(self, instance_name, status):
1379 """Set the instance's status to a given value.
1382 assert status in constants.ADMINST_ALL, \
1383 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1385 if instance_name not in self._config_data.instances:
1386 raise errors.ConfigurationError("Unknown instance '%s'" %
1388 instance = self._config_data.instances[instance_name]
1389 if instance.admin_state != status:
1390 instance.admin_state = status
1391 instance.serial_no += 1
1392 instance.mtime = time.time()
1395 @locking.ssynchronized(_config_lock)
1396 def MarkInstanceUp(self, instance_name):
1397 """Mark the instance status to up in the config.
1400 self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1402 @locking.ssynchronized(_config_lock)
1403 def MarkInstanceOffline(self, instance_name):
1404 """Mark the instance status to down in the config.
1407 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1409 @locking.ssynchronized(_config_lock)
1410 def RemoveInstance(self, instance_name):
1411 """Remove the instance from the configuration.
1414 if instance_name not in self._config_data.instances:
1415 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1417 # If a network port has been allocated to the instance,
1418 # return it to the pool of free ports.
1419 inst = self._config_data.instances[instance_name]
1420 network_port = getattr(inst, "network_port", None)
1421 if network_port is not None:
1422 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1424 instance = self._UnlockedGetInstanceInfo(instance_name)
1426 for nic in instance.nics:
1427 if nic.network is not None and nic.ip is not None:
1428 net_uuid = self._UnlockedLookupNetwork(nic.network)
1430 # Return all IP addresses to the respective address pools
1431 self._UnlockedCommitIp('release', net_uuid, nic.ip)
1434 del self._config_data.instances[instance_name]
1435 self._config_data.cluster.serial_no += 1
1438 @locking.ssynchronized(_config_lock)
1439 def RenameInstance(self, old_name, new_name):
1440 """Rename an instance.
1442 This needs to be done in ConfigWriter and not by RemoveInstance
1443 combined with AddInstance as only we can guarantee an atomic
1447 if old_name not in self._config_data.instances:
1448 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1450 # Operate on a copy to not loose instance object in case of a failure
1451 inst = self._config_data.instances[old_name].Copy()
1452 inst.name = new_name
1454 for (idx, disk) in enumerate(inst.disks):
1455 if disk.dev_type == constants.LD_FILE:
1456 # rename the file paths in logical and physical id
1457 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1458 disk.logical_id = (disk.logical_id[0],
1459 utils.PathJoin(file_storage_dir, inst.name,
1461 disk.physical_id = disk.logical_id
1463 # Actually replace instance object
1464 del self._config_data.instances[old_name]
1465 self._config_data.instances[inst.name] = inst
1467 # Force update of ssconf files
1468 self._config_data.cluster.serial_no += 1
1472 @locking.ssynchronized(_config_lock)
1473 def MarkInstanceDown(self, instance_name):
1474 """Mark the status of an instance to down in the configuration.
1477 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1479 def _UnlockedGetInstanceList(self):
1480 """Get the list of instances.
1482 This function is for internal use, when the config lock is already held.
1485 return self._config_data.instances.keys()
1487 @locking.ssynchronized(_config_lock, shared=1)
1488 def GetInstanceList(self):
1489 """Get the list of instances.
1491 @return: array of instances, ex. ['instance2.example.com',
1492 'instance1.example.com']
1495 return self._UnlockedGetInstanceList()
1497 def ExpandInstanceName(self, short_name):
1498 """Attempt to expand an incomplete instance name.
1501 # Locking is done in L{ConfigWriter.GetInstanceList}
1502 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1504 def _UnlockedGetInstanceInfo(self, instance_name):
1505 """Returns information about an instance.
1507 This function is for internal use, when the config lock is already held.
1510 if instance_name not in self._config_data.instances:
1513 return self._config_data.instances[instance_name]
1515 @locking.ssynchronized(_config_lock, shared=1)
1516 def GetInstanceInfo(self, instance_name):
1517 """Returns information about an instance.
1519 It takes the information from the configuration file. Other information of
1520 an instance are taken from the live systems.
1522 @param instance_name: name of the instance, e.g.
1523 I{instance1.example.com}
1525 @rtype: L{objects.Instance}
1526 @return: the instance object
1529 return self._UnlockedGetInstanceInfo(instance_name)
1531 @locking.ssynchronized(_config_lock, shared=1)
1532 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1533 """Returns set of node group UUIDs for instance's nodes.
1538 instance = self._UnlockedGetInstanceInfo(instance_name)
1540 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1543 nodes = [instance.primary_node]
1545 nodes = instance.all_nodes
1547 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1548 for node_name in nodes)
1550 @locking.ssynchronized(_config_lock, shared=1)
1551 def GetMultiInstanceInfo(self, instances):
1552 """Get the configuration of multiple instances.
1554 @param instances: list of instance names
1556 @return: list of tuples (instance, instance_info), where
1557 instance_info is what would GetInstanceInfo return for the
1558 node, while keeping the original order
1561 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1563 @locking.ssynchronized(_config_lock, shared=1)
1564 def GetAllInstancesInfo(self):
1565 """Get the configuration of all instances.
1568 @return: dict of (instance, instance_info), where instance_info is what
1569 would GetInstanceInfo return for the node
1572 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1573 for instance in self._UnlockedGetInstanceList()])
1576 @locking.ssynchronized(_config_lock, shared=1)
1577 def GetInstancesInfoByFilter(self, filter_fn):
1578 """Get instance configuration with a filter.
1580 @type filter_fn: callable
1581 @param filter_fn: Filter function receiving instance object as parameter,
1582 returning boolean. Important: this function is called while the
1583 configuration locks is held. It must not do any complex work or call
1584 functions potentially leading to a deadlock. Ideally it doesn't call any
1585 other functions and just compares instance attributes.
1588 return dict((name, inst)
1589 for (name, inst) in self._config_data.instances.items()
1592 @locking.ssynchronized(_config_lock)
1593 def AddNode(self, node, ec_id):
1594 """Add a node to the configuration.
1596 @type node: L{objects.Node}
1597 @param node: a Node instance
1600 logging.info("Adding node %s to configuration", node.name)
1602 self._EnsureUUID(node, ec_id)
1605 node.ctime = node.mtime = time.time()
1606 self._UnlockedAddNodeToGroup(node.name, node.group)
1607 self._config_data.nodes[node.name] = node
1608 self._config_data.cluster.serial_no += 1
1611 @locking.ssynchronized(_config_lock)
1612 def RemoveNode(self, node_name):
1613 """Remove a node from the configuration.
1616 logging.info("Removing node %s from configuration", node_name)
1618 if node_name not in self._config_data.nodes:
1619 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1621 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1622 del self._config_data.nodes[node_name]
1623 self._config_data.cluster.serial_no += 1
1626 def ExpandNodeName(self, short_name):
1627 """Attempt to expand an incomplete node name.
1630 # Locking is done in L{ConfigWriter.GetNodeList}
1631 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1633 def _UnlockedGetNodeInfo(self, node_name):
1634 """Get the configuration of a node, as stored in the config.
1636 This function is for internal use, when the config lock is already
1639 @param node_name: the node name, e.g. I{node1.example.com}
1641 @rtype: L{objects.Node}
1642 @return: the node object
1645 if node_name not in self._config_data.nodes:
1648 return self._config_data.nodes[node_name]
1650 @locking.ssynchronized(_config_lock, shared=1)
1651 def GetNodeInfo(self, node_name):
1652 """Get the configuration of a node, as stored in the config.
1654 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1656 @param node_name: the node name, e.g. I{node1.example.com}
1658 @rtype: L{objects.Node}
1659 @return: the node object
1662 return self._UnlockedGetNodeInfo(node_name)
1664 @locking.ssynchronized(_config_lock, shared=1)
1665 def GetNodeInstances(self, node_name):
1666 """Get the instances of a node, as stored in the config.
1668 @param node_name: the node name, e.g. I{node1.example.com}
1670 @rtype: (list, list)
1671 @return: a tuple with two lists: the primary and the secondary instances
1676 for inst in self._config_data.instances.values():
1677 if inst.primary_node == node_name:
1678 pri.append(inst.name)
1679 if node_name in inst.secondary_nodes:
1680 sec.append(inst.name)
1683 @locking.ssynchronized(_config_lock, shared=1)
1684 def GetNodeGroupInstances(self, uuid, primary_only=False):
1685 """Get the instances of a node group.
1687 @param uuid: Node group UUID
1688 @param primary_only: Whether to only consider primary nodes
1690 @return: List of instance names in node group
1694 nodes_fn = lambda inst: [inst.primary_node]
1696 nodes_fn = lambda inst: inst.all_nodes
1698 return frozenset(inst.name
1699 for inst in self._config_data.instances.values()
1700 for node_name in nodes_fn(inst)
1701 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1703 def _UnlockedGetNodeList(self):
1704 """Return the list of nodes which are in the configuration.
1706 This function is for internal use, when the config lock is already
1712 return self._config_data.nodes.keys()
1714 @locking.ssynchronized(_config_lock, shared=1)
1715 def GetNodeList(self):
1716 """Return the list of nodes which are in the configuration.
1719 return self._UnlockedGetNodeList()
1721 def _UnlockedGetOnlineNodeList(self):
1722 """Return the list of nodes which are online.
1725 all_nodes = [self._UnlockedGetNodeInfo(node)
1726 for node in self._UnlockedGetNodeList()]
1727 return [node.name for node in all_nodes if not node.offline]
1729 @locking.ssynchronized(_config_lock, shared=1)
1730 def GetOnlineNodeList(self):
1731 """Return the list of nodes which are online.
1734 return self._UnlockedGetOnlineNodeList()
1736 @locking.ssynchronized(_config_lock, shared=1)
1737 def GetVmCapableNodeList(self):
1738 """Return the list of nodes which are not vm capable.
1741 all_nodes = [self._UnlockedGetNodeInfo(node)
1742 for node in self._UnlockedGetNodeList()]
1743 return [node.name for node in all_nodes if node.vm_capable]
1745 @locking.ssynchronized(_config_lock, shared=1)
1746 def GetNonVmCapableNodeList(self):
1747 """Return the list of nodes which are not vm capable.
1750 all_nodes = [self._UnlockedGetNodeInfo(node)
1751 for node in self._UnlockedGetNodeList()]
1752 return [node.name for node in all_nodes if not node.vm_capable]
1754 @locking.ssynchronized(_config_lock, shared=1)
1755 def GetMultiNodeInfo(self, nodes):
1756 """Get the configuration of multiple nodes.
1758 @param nodes: list of node names
1760 @return: list of tuples of (node, node_info), where node_info is
1761 what would GetNodeInfo return for the node, in the original
1765 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1767 @locking.ssynchronized(_config_lock, shared=1)
1768 def GetAllNodesInfo(self):
1769 """Get the configuration of all nodes.
1772 @return: dict of (node, node_info), where node_info is what
1773 would GetNodeInfo return for the node
1776 return self._UnlockedGetAllNodesInfo()
1778 def _UnlockedGetAllNodesInfo(self):
1779 """Gets configuration of all nodes.
1781 @note: See L{GetAllNodesInfo}
1784 return dict([(node, self._UnlockedGetNodeInfo(node))
1785 for node in self._UnlockedGetNodeList()])
1787 @locking.ssynchronized(_config_lock, shared=1)
1788 def GetNodeGroupsFromNodes(self, nodes):
1789 """Returns groups for a list of nodes.
1791 @type nodes: list of string
1792 @param nodes: List of node names
1796 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1798 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1799 """Get the number of current and maximum desired and possible candidates.
1801 @type exceptions: list
1802 @param exceptions: if passed, list of nodes that should be ignored
1804 @return: tuple of (current, desired and possible, possible)
1807 mc_now = mc_should = mc_max = 0
1808 for node in self._config_data.nodes.values():
1809 if exceptions and node.name in exceptions:
1811 if not (node.offline or node.drained) and node.master_capable:
1813 if node.master_candidate:
1815 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1816 return (mc_now, mc_should, mc_max)
1818 @locking.ssynchronized(_config_lock, shared=1)
1819 def GetMasterCandidateStats(self, exceptions=None):
1820 """Get the number of current and maximum possible candidates.
1822 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1824 @type exceptions: list
1825 @param exceptions: if passed, list of nodes that should be ignored
1827 @return: tuple of (current, max)
1830 return self._UnlockedGetMasterCandidateStats(exceptions)
1832 @locking.ssynchronized(_config_lock)
1833 def MaintainCandidatePool(self, exceptions):
1834 """Try to grow the candidate pool to the desired size.
1836 @type exceptions: list
1837 @param exceptions: if passed, list of nodes that should be ignored
1839 @return: list with the adjusted nodes (L{objects.Node} instances)
1842 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1845 node_list = self._config_data.nodes.keys()
1846 random.shuffle(node_list)
1847 for name in node_list:
1848 if mc_now >= mc_max:
1850 node = self._config_data.nodes[name]
1851 if (node.master_candidate or node.offline or node.drained or
1852 node.name in exceptions or not node.master_capable):
1854 mod_list.append(node)
1855 node.master_candidate = True
1858 if mc_now != mc_max:
1859 # this should not happen
1860 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1861 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1863 self._config_data.cluster.serial_no += 1
1868 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1869 """Add a given node to the specified group.
1872 if nodegroup_uuid not in self._config_data.nodegroups:
1873 # This can happen if a node group gets deleted between its lookup and
1874 # when we're adding the first node to it, since we don't keep a lock in
1875 # the meantime. It's ok though, as we'll fail cleanly if the node group
1876 # is not found anymore.
1877 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1878 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1879 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1881 def _UnlockedRemoveNodeFromGroup(self, node):
1882 """Remove a given node from its group.
1885 nodegroup = node.group
1886 if nodegroup not in self._config_data.nodegroups:
1887 logging.warning("Warning: node '%s' has unknown node group '%s'"
1888 " (while being removed from it)", node.name, nodegroup)
1889 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1890 if node.name not in nodegroup_obj.members:
1891 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1892 " (while being removed from it)", node.name, nodegroup)
1894 nodegroup_obj.members.remove(node.name)
1896 @locking.ssynchronized(_config_lock)
1897 def AssignGroupNodes(self, mods):
1898 """Changes the group of a number of nodes.
1900 @type mods: list of tuples; (node name, new group UUID)
1901 @param mods: Node membership modifications
1904 groups = self._config_data.nodegroups
1905 nodes = self._config_data.nodes
1909 # Try to resolve names/UUIDs first
1910 for (node_name, new_group_uuid) in mods:
1912 node = nodes[node_name]
1914 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1916 if node.group == new_group_uuid:
1917 # Node is being assigned to its current group
1918 logging.debug("Node '%s' was assigned to its current group (%s)",
1919 node_name, node.group)
1922 # Try to find current group of node
1924 old_group = groups[node.group]
1926 raise errors.ConfigurationError("Unable to find old group '%s'" %
1929 # Try to find new group for node
1931 new_group = groups[new_group_uuid]
1933 raise errors.ConfigurationError("Unable to find new group '%s'" %
1936 assert node.name in old_group.members, \
1937 ("Inconsistent configuration: node '%s' not listed in members for its"
1938 " old group '%s'" % (node.name, old_group.uuid))
1939 assert node.name not in new_group.members, \
1940 ("Inconsistent configuration: node '%s' already listed in members for"
1941 " its new group '%s'" % (node.name, new_group.uuid))
1943 resmod.append((node, old_group, new_group))
1946 for (node, old_group, new_group) in resmod:
1947 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1948 "Assigning to current group is not possible"
1950 node.group = new_group.uuid
1952 # Update members of involved groups
1953 if node.name in old_group.members:
1954 old_group.members.remove(node.name)
1955 if node.name not in new_group.members:
1956 new_group.members.append(node.name)
1958 # Update timestamps and serials (only once per node/group object)
1960 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
1964 # Force ssconf update
1965 self._config_data.cluster.serial_no += 1
1969 def _BumpSerialNo(self):
1970 """Bump up the serial number of the config.
1973 self._config_data.serial_no += 1
1974 self._config_data.mtime = time.time()
1976 def _AllUUIDObjects(self):
1977 """Returns all objects with uuid attributes.
1980 return (self._config_data.instances.values() +
1981 self._config_data.nodes.values() +
1982 self._config_data.nodegroups.values() +
1983 [self._config_data.cluster])
1985 def _OpenConfig(self, accept_foreign):
1986 """Read the config data from disk.
1989 raw_data = utils.ReadFile(self._cfg_file)
1992 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1993 except Exception, err:
1994 raise errors.ConfigurationError(err)
1996 # Make sure the configuration has the right version
1997 _ValidateConfig(data)
1999 if (not hasattr(data, "cluster") or
2000 not hasattr(data.cluster, "rsahostkeypub")):
2001 raise errors.ConfigurationError("Incomplete configuration"
2002 " (missing cluster.rsahostkeypub)")
2004 if data.cluster.master_node != self._my_hostname and not accept_foreign:
2005 msg = ("The configuration denotes node %s as master, while my"
2006 " hostname is %s; opening a foreign configuration is only"
2007 " possible in accept_foreign mode" %
2008 (data.cluster.master_node, self._my_hostname))
2009 raise errors.ConfigurationError(msg)
2011 # Upgrade configuration if needed
2012 data.UpgradeConfig()
2014 self._config_data = data
2015 # reset the last serial as -1 so that the next write will cause
2017 self._last_cluster_serial = -1
2019 # And finally run our (custom) config upgrade sequence
2020 self._UpgradeConfig()
2022 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2024 def _UpgradeConfig(self):
2025 """Run upgrade steps that cannot be done purely in the objects.
2027 This is because some data elements need uniqueness across the
2028 whole configuration, etc.
2030 @warning: this function will call L{_WriteConfig()}, but also
2031 L{DropECReservations} so it needs to be called only from a
2032 "safe" place (the constructor). If one wanted to call it with
2033 the lock held, a DropECReservationUnlocked would need to be
2034 created first, to avoid causing deadlock.
2038 for item in self._AllUUIDObjects():
2039 if item.uuid is None:
2040 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2042 if not self._config_data.nodegroups:
2043 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2044 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2046 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2048 for node in self._config_data.nodes.values():
2050 node.group = self.LookupNodeGroup(None)
2052 # This is technically *not* an upgrade, but needs to be done both when
2053 # nodegroups are being added, and upon normally loading the config,
2054 # because the members list of a node group is discarded upon
2055 # serializing/deserializing the object.
2056 self._UnlockedAddNodeToGroup(node.name, node.group)
2059 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2060 # only called at config init time, without the lock held
2061 self.DropECReservations(_UPGRADE_CONFIG_JID)
2063 def _DistributeConfig(self, feedback_fn):
2064 """Distribute the configuration to the other nodes.
2066 Currently, this only copies the configuration file. In the future,
2067 it could be used to encapsulate the 2/3-phase update mechanism.
2077 myhostname = self._my_hostname
2078 # we can skip checking whether _UnlockedGetNodeInfo returns None
2079 # since the node list comes from _UnlocketGetNodeList, and we are
2080 # called with the lock held, so no modifications should take place
2082 for node_name in self._UnlockedGetNodeList():
2083 if node_name == myhostname:
2085 node_info = self._UnlockedGetNodeInfo(node_name)
2086 if not node_info.master_candidate:
2088 node_list.append(node_info.name)
2089 addr_list.append(node_info.primary_ip)
2091 # TODO: Use dedicated resolver talking to config writer for name resolution
2093 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2094 for to_node, to_result in result.items():
2095 msg = to_result.fail_msg
2097 msg = ("Copy of file %s to node %s failed: %s" %
2098 (self._cfg_file, to_node, msg))
2108 def _WriteConfig(self, destination=None, feedback_fn=None):
2109 """Write the configuration data to persistent storage.
2112 assert feedback_fn is None or callable(feedback_fn)
2114 # Warn on config errors, but don't abort the save - the
2115 # configuration has already been modified, and we can't revert;
2116 # the best we can do is to warn the user and save as is, leaving
2117 # recovery to the user
2118 config_errors = self._UnlockedVerifyConfig()
2120 errmsg = ("Configuration data is not consistent: %s" %
2121 (utils.CommaJoin(config_errors)))
2122 logging.critical(errmsg)
2126 if destination is None:
2127 destination = self._cfg_file
2128 self._BumpSerialNo()
2129 txt = serializer.Dump(self._config_data.ToDict())
2131 getents = self._getents()
2133 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2134 close=False, gid=getents.confd_gid, mode=0640)
2135 except errors.LockError:
2136 raise errors.ConfigurationError("The configuration file has been"
2137 " modified since the last write, cannot"
2140 self._cfg_id = utils.GetFileID(fd=fd)
2144 self.write_count += 1
2146 # and redistribute the config file to master candidates
2147 self._DistributeConfig(feedback_fn)
2149 # Write ssconf files on all nodes (including locally)
2150 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2151 if not self._offline:
2152 result = self._GetRpc(None).call_write_ssconf_files(
2153 self._UnlockedGetOnlineNodeList(),
2154 self._UnlockedGetSsconfValues())
2156 for nname, nresu in result.items():
2157 msg = nresu.fail_msg
2159 errmsg = ("Error while uploading ssconf files to"
2160 " node %s: %s" % (nname, msg))
2161 logging.warning(errmsg)
2166 self._last_cluster_serial = self._config_data.cluster.serial_no
2168 def _UnlockedGetSsconfValues(self):
2169 """Return the values needed by ssconf.
2172 @return: a dictionary with keys the ssconf names and values their
2177 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2178 node_names = utils.NiceSort(self._UnlockedGetNodeList())
2179 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2180 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2181 for ninfo in node_info]
2182 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2183 for ninfo in node_info]
2185 instance_data = fn(instance_names)
2186 off_data = fn(node.name for node in node_info if node.offline)
2187 on_data = fn(node.name for node in node_info if not node.offline)
2188 mc_data = fn(node.name for node in node_info if node.master_candidate)
2189 mc_ips_data = fn(node.primary_ip for node in node_info
2190 if node.master_candidate)
2191 node_data = fn(node_names)
2192 node_pri_ips_data = fn(node_pri_ips)
2193 node_snd_ips_data = fn(node_snd_ips)
2195 cluster = self._config_data.cluster
2196 cluster_tags = fn(cluster.GetTags())
2198 hypervisor_list = fn(cluster.enabled_hypervisors)
2200 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2202 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2203 self._config_data.nodegroups.values()]
2204 nodegroups_data = fn(utils.NiceSort(nodegroups))
2205 networks = ["%s %s" % (net.uuid, net.name) for net in
2206 self._config_data.networks.values()]
2207 networks_data = fn(utils.NiceSort(networks))
2210 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2211 constants.SS_CLUSTER_TAGS: cluster_tags,
2212 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2213 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2214 constants.SS_MASTER_CANDIDATES: mc_data,
2215 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2216 constants.SS_MASTER_IP: cluster.master_ip,
2217 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2218 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2219 constants.SS_MASTER_NODE: cluster.master_node,
2220 constants.SS_NODE_LIST: node_data,
2221 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2222 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2223 constants.SS_OFFLINE_NODES: off_data,
2224 constants.SS_ONLINE_NODES: on_data,
2225 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2226 constants.SS_INSTANCE_LIST: instance_data,
2227 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2228 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2229 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2230 constants.SS_UID_POOL: uid_pool,
2231 constants.SS_NODEGROUPS: nodegroups_data,
2232 constants.SS_NETWORKS: networks_data,
2234 bad_values = [(k, v) for k, v in ssconf_values.items()
2235 if not isinstance(v, (str, basestring))]
2237 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2238 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2239 " values: %s" % err)
2240 return ssconf_values
2242 @locking.ssynchronized(_config_lock, shared=1)
2243 def GetSsconfValues(self):
2244 """Wrapper using lock around _UnlockedGetSsconf().
2247 return self._UnlockedGetSsconfValues()
2249 @locking.ssynchronized(_config_lock, shared=1)
2250 def GetVGName(self):
2251 """Return the volume group name.
2254 return self._config_data.cluster.volume_group_name
2256 @locking.ssynchronized(_config_lock)
2257 def SetVGName(self, vg_name):
2258 """Set the volume group name.
2261 self._config_data.cluster.volume_group_name = vg_name
2262 self._config_data.cluster.serial_no += 1
2265 @locking.ssynchronized(_config_lock, shared=1)
2266 def GetDRBDHelper(self):
2267 """Return DRBD usermode helper.
2270 return self._config_data.cluster.drbd_usermode_helper
2272 @locking.ssynchronized(_config_lock)
2273 def SetDRBDHelper(self, drbd_helper):
2274 """Set DRBD usermode helper.
2277 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2278 self._config_data.cluster.serial_no += 1
2281 @locking.ssynchronized(_config_lock, shared=1)
2282 def GetMACPrefix(self):
2283 """Return the mac prefix.
2286 return self._config_data.cluster.mac_prefix
2288 @locking.ssynchronized(_config_lock, shared=1)
2289 def GetClusterInfo(self):
2290 """Returns information about the cluster
2292 @rtype: L{objects.Cluster}
2293 @return: the cluster object
2296 return self._config_data.cluster
2298 @locking.ssynchronized(_config_lock, shared=1)
2299 def HasAnyDiskOfType(self, dev_type):
2300 """Check if in there is at disk of the given type in the configuration.
2303 return self._config_data.HasAnyDiskOfType(dev_type)
2305 @locking.ssynchronized(_config_lock)
2306 def Update(self, target, feedback_fn, ec_id=None):
2307 """Notify function to be called after updates.
2309 This function must be called when an object (as returned by
2310 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2311 caller wants the modifications saved to the backing store. Note
2312 that all modified objects will be saved, but the target argument
2313 is the one the caller wants to ensure that it's saved.
2315 @param target: an instance of either L{objects.Cluster},
2316 L{objects.Node} or L{objects.Instance} which is existing in
2318 @param feedback_fn: Callable feedback function
2321 if self._config_data is None:
2322 raise errors.ProgrammerError("Configuration file not read,"
2324 update_serial = False
2325 if isinstance(target, objects.Cluster):
2326 test = target == self._config_data.cluster
2327 elif isinstance(target, objects.Node):
2328 test = target in self._config_data.nodes.values()
2329 update_serial = True
2330 elif isinstance(target, objects.Instance):
2331 test = target in self._config_data.instances.values()
2332 elif isinstance(target, objects.NodeGroup):
2333 test = target in self._config_data.nodegroups.values()
2334 elif isinstance(target, objects.Network):
2335 test = target in self._config_data.networks.values()
2337 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2338 " ConfigWriter.Update" % type(target))
2340 raise errors.ConfigurationError("Configuration updated since object"
2341 " has been read or unknown object")
2342 target.serial_no += 1
2343 target.mtime = now = time.time()
2346 # for node updates, we need to increase the cluster serial too
2347 self._config_data.cluster.serial_no += 1
2348 self._config_data.cluster.mtime = now
2350 if isinstance(target, objects.Instance):
2351 self._UnlockedReleaseDRBDMinors(target.name)
2353 if ec_id is not None:
2354 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2355 self._UnlockedCommitTemporaryIps(ec_id)
2357 self._WriteConfig(feedback_fn=feedback_fn)
2359 @locking.ssynchronized(_config_lock)
2360 def DropECReservations(self, ec_id):
2361 """Drop per-execution-context reservations
2364 for rm in self._all_rms:
2365 rm.DropECReservations(ec_id)
2367 @locking.ssynchronized(_config_lock, shared=1)
2368 def GetAllNetworksInfo(self):
2369 """Get the configuration of all networks
2372 return dict(self._config_data.networks)
2374 def _UnlockedGetNetworkList(self):
2375 """Get the list of networks.
2377 This function is for internal use, when the config lock is already held.
2380 return self._config_data.networks.keys()
2382 @locking.ssynchronized(_config_lock, shared=1)
2383 def GetNetworkList(self):
2384 """Get the list of networks.
2386 @return: array of networks, ex. ["main", "vlan100", "200]
2389 return self._UnlockedGetNetworkList()
2391 @locking.ssynchronized(_config_lock, shared=1)
2392 def GetNetworkNames(self):
2393 """Get a list of network names
2396 names = [network.name
2397 for network in self._config_data.networks.values()]
2400 def _UnlockedGetNetwork(self, uuid):
2401 """Returns information about a network.
2403 This function is for internal use, when the config lock is already held.
2406 if uuid not in self._config_data.networks:
2409 return self._config_data.networks[uuid]
2411 @locking.ssynchronized(_config_lock, shared=1)
2412 def GetNetwork(self, uuid):
2413 """Returns information about a network.
2415 It takes the information from the configuration file.
2417 @param uuid: UUID of the network
2419 @rtype: L{objects.Network}
2420 @return: the network object
2423 return self._UnlockedGetNetwork(uuid)
2425 @locking.ssynchronized(_config_lock)
2426 def AddNetwork(self, net, ec_id, check_uuid=True):
2427 """Add a network to the configuration.
2429 @type net: L{objects.Network}
2430 @param net: the Network object to add
2432 @param ec_id: unique id for the job to use when creating a missing UUID
2435 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2438 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2439 """Add a network to the configuration.
2442 logging.info("Adding network %s to configuration", net.name)
2445 self._EnsureUUID(net, ec_id)
2447 existing_uuid = self._UnlockedLookupNetwork(net.name)
2449 raise errors.OpPrereqError("Desired network name '%s' already"
2450 " exists as a network (UUID: %s)" %
2451 (net.name, existing_uuid),
2452 errors.ECODE_EXISTS)
2454 self._config_data.networks[net.uuid] = net
2455 self._config_data.cluster.serial_no += 1
2457 def _UnlockedLookupNetwork(self, target):
2458 """Lookup a network's UUID.
2460 @type target: string
2461 @param target: network name or UUID
2463 @return: network UUID
2464 @raises errors.OpPrereqError: when the target network cannot be found
2467 if target in self._config_data.networks:
2469 for net in self._config_data.networks.values():
2470 if net.name == target:
2474 @locking.ssynchronized(_config_lock, shared=1)
2475 def LookupNetwork(self, target):
2476 """Lookup a network's UUID.
2478 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2480 @type target: string
2481 @param target: network name or UUID
2483 @return: network UUID
2486 return self._UnlockedLookupNetwork(target)
2488 @locking.ssynchronized(_config_lock)
2489 def RemoveNetwork(self, network_uuid):
2490 """Remove a network from the configuration.
2492 @type network_uuid: string
2493 @param network_uuid: the UUID of the network to remove
2496 logging.info("Removing network %s from configuration", network_uuid)
2498 if network_uuid not in self._config_data.networks:
2499 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2501 del self._config_data.networks[network_uuid]
2502 self._config_data.cluster.serial_no += 1
2505 def _UnlockedGetGroupNetParams(self, net, node):
2506 """Get the netparams (mode, link) of a network.
2508 Get a network's netparams for a given node.
2511 @param net: network name
2513 @param node: node name
2514 @rtype: dict or None
2518 net_uuid = self._UnlockedLookupNetwork(net)
2519 if net_uuid is None:
2522 node_info = self._UnlockedGetNodeInfo(node)
2523 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2524 netparams = nodegroup_info.networks.get(net_uuid, None)
2528 @locking.ssynchronized(_config_lock, shared=1)
2529 def GetGroupNetParams(self, net, node):
2530 """Locking wrapper of _UnlockedGetGroupNetParams()
2533 return self._UnlockedGetGroupNetParams(net, node)
2536 @locking.ssynchronized(_config_lock, shared=1)
2537 def CheckIPInNodeGroup(self, ip, node):
2538 """Check for conflictig IP.
2541 @param ip: ip address
2543 @param node: node name
2544 @rtype: (string, dict) or (None, None)
2545 @return: (network name, netparams)
2550 node_info = self._UnlockedGetNodeInfo(node)
2551 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2552 for net_uuid in nodegroup_info.networks.keys():
2553 net_info = self._UnlockedGetNetwork(net_uuid)
2554 pool = network.AddressPool(net_info)
2555 if pool._Contains(ip):
2556 return (net_info.name, nodegroup_info.networks[net_uuid])