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
42 from functools import wraps
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):
114 if ec_id in self._ec_reserved:
115 ec_reserved.update(self._ec_reserved[ec_id])
119 def Generate(self, existing, generate_one_fn, ec_id):
120 """Generate a new resource of this type
123 assert callable(generate_one_fn)
125 all_elems = self.GetReserved()
126 all_elems.update(existing)
129 new_resource = generate_one_fn()
130 if new_resource is not None and new_resource not in all_elems:
133 raise errors.ConfigurationError("Not able generate new resource"
134 " (last tried: %s)" % new_resource)
135 self.Reserve(ec_id, new_resource)
139 def _MatchNameComponentIgnoreCase(short_name, names):
140 """Wrapper around L{utils.text.MatchNameComponent}.
143 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
146 def _CheckInstanceDiskIvNames(disks):
147 """Checks if instance's disks' C{iv_name} attributes are in order.
149 @type disks: list of L{objects.Disk}
150 @param disks: List of disks
151 @rtype: list of tuples; (int, string, string)
152 @return: List of wrongly named disks, each tuple contains disk index,
153 expected and actual name
158 for (idx, disk) in enumerate(disks):
159 exp_iv_name = "disk/%s" % idx
160 if disk.iv_name != exp_iv_name:
161 result.append((idx, exp_iv_name, disk.iv_name))
167 """The interface to the cluster configuration.
169 @ivar _temporary_lvs: reservation manager for temporary LVs
170 @ivar _all_rms: a list of all temporary reservation managers
173 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
174 accept_foreign=False):
176 self._lock = _config_lock
177 self._config_data = None
178 self._offline = offline
180 self._cfg_file = pathutils.CLUSTER_CONF_FILE
182 self._cfg_file = cfg_file
183 self._getents = _getents
184 self._temporary_ids = TemporaryReservationManager()
185 self._temporary_drbds = {}
186 self._temporary_macs = TemporaryReservationManager()
187 self._temporary_secrets = TemporaryReservationManager()
188 self._temporary_lvs = TemporaryReservationManager()
189 self._temporary_ips = TemporaryReservationManager()
190 self._all_rms = [self._temporary_ids, self._temporary_macs,
191 self._temporary_secrets, self._temporary_lvs,
193 # Note: in order to prevent errors when resolving our name in
194 # _DistributeConfig, we compute it here once and reuse it; it's
195 # better to raise an error before starting to modify the config
196 # file than after it was modified
197 self._my_hostname = netutils.Hostname.GetSysName()
198 self._last_cluster_serial = -1
201 self._OpenConfig(accept_foreign)
203 def _GetRpc(self, address_list):
204 """Returns RPC runner for configuration.
207 return rpc.ConfigRunner(self._context, address_list)
209 def SetContext(self, context):
210 """Sets Ganeti context.
213 self._context = context
215 # this method needs to be static, so that we can call it on the class
218 """Check if the cluster is configured.
221 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
223 def _GenerateMACPrefix(self, net=None):
224 def _get_mac_prefix(view_func):
225 def _decorator(*args, **kwargs):
226 prefix = self._config_data.cluster.mac_prefix
228 net_uuid = self._UnlockedLookupNetwork(net)
230 nobj = self._UnlockedGetNetwork(net_uuid)
232 prefix = nobj.mac_prefix
233 suffix = view_func(*args, **kwargs)
234 return prefix+':'+suffix
235 return wraps(view_func)(_decorator)
236 return _get_mac_prefix
238 def _GenerateMACSuffix(self):
239 """Generate one mac address
242 byte1 = random.randrange(0, 256)
243 byte2 = random.randrange(0, 256)
244 byte3 = random.randrange(0, 256)
245 suffix = "%02x:%02x:%02x" % (byte1, byte2, byte3)
248 @locking.ssynchronized(_config_lock, shared=1)
249 def GetNdParams(self, node):
250 """Get the node params populated with cluster defaults.
252 @type node: L{objects.Node}
253 @param node: The node we want to know the params for
254 @return: A dict with the filled in node params
257 nodegroup = self._UnlockedGetNodeGroup(node.group)
258 return self._config_data.cluster.FillND(node, nodegroup)
260 @locking.ssynchronized(_config_lock, shared=1)
261 def GetInstanceDiskParams(self, instance):
262 """Get the disk params populated with inherit chain.
264 @type instance: L{objects.Instance}
265 @param instance: The instance we want to know the params for
266 @return: A dict with the filled in disk params
269 node = self._UnlockedGetNodeInfo(instance.primary_node)
270 nodegroup = self._UnlockedGetNodeGroup(node.group)
271 return self._UnlockedGetGroupDiskParams(nodegroup)
273 @locking.ssynchronized(_config_lock, shared=1)
274 def GetGroupDiskParams(self, group):
275 """Get the disk params populated with inherit chain.
277 @type group: L{objects.NodeGroup}
278 @param group: The group we want to know the params for
279 @return: A dict with the filled in disk params
282 return self._UnlockedGetGroupDiskParams(group)
284 def _UnlockedGetGroupDiskParams(self, group):
285 """Get the disk params populated with inherit chain down to node-group.
287 @type group: L{objects.NodeGroup}
288 @param group: The group we want to know the params for
289 @return: A dict with the filled in disk params
292 return self._config_data.cluster.SimpleFillDP(group.diskparams)
294 @locking.ssynchronized(_config_lock, shared=1)
295 def GenerateMAC(self, net, ec_id):
296 """Generate a MAC for an instance.
298 This should check the current instances for duplicates.
301 existing = self._AllMACs()
302 gen_mac = self._GenerateMACPrefix(net)(self._GenerateMACSuffix)
303 return self._temporary_ids.Generate(existing, gen_mac, ec_id)
305 @locking.ssynchronized(_config_lock, shared=1)
306 def ReserveMAC(self, mac, ec_id):
307 """Reserve a MAC for an instance.
309 This only checks instances managed by this cluster, it does not
310 check for potential collisions elsewhere.
313 all_macs = self._AllMACs()
315 raise errors.ReservationError("mac already in use")
317 self._temporary_macs.Reserve(ec_id, mac)
319 def _UnlockedCommitTemporaryIps(self, ec_id):
320 """Commit all reserved IP address to their respective pools
323 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
324 self._UnlockedCommitIp(action, net_uuid, address)
326 def _UnlockedCommitIp(self, action, net_uuid, address):
327 """Commit a reserved IP address to an IP pool.
329 The IP address is taken from the network's IP pool and marked as reserved.
332 nobj = self._UnlockedGetNetwork(net_uuid)
333 pool = network.AddressPool(nobj)
334 if action == 'reserve':
335 pool.Reserve(address)
336 elif action == 'release':
337 pool.Release(address)
339 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
340 """Give a specific IP address back to an IP pool.
342 The IP address is returned to the IP pool designated by pool_id and marked
346 nobj = self._UnlockedGetNetwork(net_uuid)
347 pool = network.AddressPool(nobj)
348 self._temporary_ips.Reserve(ec_id, ('release', address, net_uuid))
350 @locking.ssynchronized(_config_lock, shared=1)
351 def ReleaseIp(self, network, address, ec_id):
352 """Give a specified IP address back to an IP pool.
354 This is just a wrapper around _UnlockedReleaseIp.
357 net_uuid = self._UnlockedLookupNetwork(network)
359 self._UnlockedReleaseIp(net_uuid, address, ec_id)
361 @locking.ssynchronized(_config_lock, shared=1)
362 def GenerateIp(self, net, ec_id):
363 """Find a free IPv4 address for an instance.
366 net_uuid = self._UnlockedLookupNetwork(net)
367 nobj = self._UnlockedGetNetwork(net_uuid)
368 pool = network.AddressPool(nobj)
369 gen_free = pool.GenerateFree()
374 except StopIteration:
375 raise errors.ReservationError("Cannot generate IP. Network is full")
376 return ("reserve", ip, net_uuid)
378 _ ,address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
381 def _UnlockedReserveIp(self, net_uuid, address, ec_id):
382 """Reserve a given IPv4 address for use by an instance.
385 nobj = self._UnlockedGetNetwork(net_uuid)
386 pool = network.AddressPool(nobj)
388 isreserved = pool.IsReserved(address)
389 except errors.AddressPoolError:
390 raise errors.ReservationError("IP address not in network")
392 raise errors.ReservationError("IP address already in use")
394 return self._temporary_ips.Reserve(ec_id, ('reserve', address, net_uuid))
397 @locking.ssynchronized(_config_lock, shared=1)
398 def ReserveIp(self, net, address, ec_id):
399 """Reserve a given IPv4 address for use by an instance.
402 net_uuid = self._UnlockedLookupNetwork(net)
404 return self._UnlockedReserveIp(net_uuid, address, ec_id)
406 @locking.ssynchronized(_config_lock, shared=1)
407 def ReserveLV(self, lv_name, ec_id):
408 """Reserve an VG/LV pair for an instance.
410 @type lv_name: string
411 @param lv_name: the logical volume name to reserve
414 all_lvs = self._AllLVs()
415 if lv_name in all_lvs:
416 raise errors.ReservationError("LV already in use")
418 self._temporary_lvs.Reserve(ec_id, lv_name)
420 @locking.ssynchronized(_config_lock, shared=1)
421 def GenerateDRBDSecret(self, ec_id):
422 """Generate a DRBD secret.
424 This checks the current disks for duplicates.
427 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
428 utils.GenerateSecret,
432 """Compute the list of all LVs.
436 for instance in self._config_data.instances.values():
437 node_data = instance.MapLVsByNode()
438 for lv_list in node_data.values():
439 lvnames.update(lv_list)
442 def _AllIDs(self, include_temporary):
443 """Compute the list of all UUIDs and names we have.
445 @type include_temporary: boolean
446 @param include_temporary: whether to include the _temporary_ids set
448 @return: a set of IDs
452 if include_temporary:
453 existing.update(self._temporary_ids.GetReserved())
454 existing.update(self._AllLVs())
455 existing.update(self._config_data.instances.keys())
456 existing.update(self._config_data.nodes.keys())
457 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
460 def _GenerateUniqueID(self, ec_id):
461 """Generate an unique UUID.
463 This checks the current node, instances and disk names for
467 @return: the unique id
470 existing = self._AllIDs(include_temporary=False)
471 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
473 @locking.ssynchronized(_config_lock, shared=1)
474 def GenerateUniqueID(self, ec_id):
475 """Generate an unique ID.
477 This is just a wrapper over the unlocked version.
480 @param ec_id: unique id for the job to reserve the id to
483 return self._GenerateUniqueID(ec_id)
486 """Return all MACs present in the config.
489 @return: the list of all MACs
493 for instance in self._config_data.instances.values():
494 for nic in instance.nics:
495 result.append(nic.mac)
499 def _AllDRBDSecrets(self):
500 """Return all DRBD secrets present in the config.
503 @return: the list of all DRBD secrets
506 def helper(disk, result):
507 """Recursively gather secrets from this disk."""
508 if disk.dev_type == constants.DT_DRBD8:
509 result.append(disk.logical_id[5])
511 for child in disk.children:
512 helper(child, result)
515 for instance in self._config_data.instances.values():
516 for disk in instance.disks:
521 def _CheckDiskIDs(self, disk, l_ids, p_ids):
522 """Compute duplicate disk IDs
524 @type disk: L{objects.Disk}
525 @param disk: the disk at which to start searching
527 @param l_ids: list of current logical ids
529 @param p_ids: list of current physical ids
531 @return: a list of error messages
535 if disk.logical_id is not None:
536 if disk.logical_id in l_ids:
537 result.append("duplicate logical id %s" % str(disk.logical_id))
539 l_ids.append(disk.logical_id)
540 if disk.physical_id is not None:
541 if disk.physical_id in p_ids:
542 result.append("duplicate physical id %s" % str(disk.physical_id))
544 p_ids.append(disk.physical_id)
547 for child in disk.children:
548 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
551 def _UnlockedVerifyConfig(self):
555 @return: a list of error messages; a non-empty list signifies
559 # pylint: disable=R0914
563 data = self._config_data
564 cluster = data.cluster
568 # global cluster checks
569 if not cluster.enabled_hypervisors:
570 result.append("enabled hypervisors list doesn't have any entries")
571 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
573 result.append("enabled hypervisors contains invalid entries: %s" %
575 missing_hvp = (set(cluster.enabled_hypervisors) -
576 set(cluster.hvparams.keys()))
578 result.append("hypervisor parameters missing for the enabled"
579 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
581 if cluster.master_node not in data.nodes:
582 result.append("cluster has invalid primary node '%s'" %
585 def _helper(owner, attr, value, template):
587 utils.ForceDictType(value, template)
588 except errors.GenericError, err:
589 result.append("%s has invalid %s: %s" % (owner, attr, err))
591 def _helper_nic(owner, params):
593 objects.NIC.CheckParameterSyntax(params)
594 except errors.ConfigurationError, err:
595 result.append("%s has invalid nicparams: %s" % (owner, err))
597 def _helper_ipolicy(owner, params, check_std):
599 objects.InstancePolicy.CheckParameterSyntax(params, check_std)
600 except errors.ConfigurationError, err:
601 result.append("%s has invalid instance policy: %s" % (owner, err))
603 def _helper_ispecs(owner, params):
604 for key, value in params.items():
605 if key in constants.IPOLICY_ISPECS:
606 fullkey = "ipolicy/" + key
607 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
609 # FIXME: assuming list type
610 if key in constants.IPOLICY_PARAMETERS:
614 if not isinstance(value, exp_type):
615 result.append("%s has invalid instance policy: for %s,"
616 " expecting %s, got %s" %
617 (owner, key, exp_type.__name__, type(value)))
619 # check cluster parameters
620 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
621 constants.BES_PARAMETER_TYPES)
622 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
623 constants.NICS_PARAMETER_TYPES)
624 _helper_nic("cluster", cluster.SimpleFillNIC({}))
625 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
626 constants.NDS_PARAMETER_TYPES)
627 _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
628 _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
630 # per-instance checks
631 for instance_name in data.instances:
632 instance = data.instances[instance_name]
633 if instance.name != instance_name:
634 result.append("instance '%s' is indexed by wrong name '%s'" %
635 (instance.name, instance_name))
636 if instance.primary_node not in data.nodes:
637 result.append("instance '%s' has invalid primary node '%s'" %
638 (instance_name, instance.primary_node))
639 for snode in instance.secondary_nodes:
640 if snode not in data.nodes:
641 result.append("instance '%s' has invalid secondary node '%s'" %
642 (instance_name, snode))
643 for idx, nic in enumerate(instance.nics):
644 if nic.mac in seen_macs:
645 result.append("instance '%s' has NIC %d mac %s duplicate" %
646 (instance_name, idx, nic.mac))
648 seen_macs.append(nic.mac)
650 filled = cluster.SimpleFillNIC(nic.nicparams)
651 owner = "instance %s nic %d" % (instance.name, idx)
652 _helper(owner, "nicparams",
653 filled, constants.NICS_PARAMETER_TYPES)
654 _helper_nic(owner, filled)
657 if instance.beparams:
658 _helper("instance %s" % instance.name, "beparams",
659 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
661 # gather the drbd ports for duplicate checks
662 for (idx, dsk) in enumerate(instance.disks):
663 if dsk.dev_type in constants.LDS_DRBD:
664 tcp_port = dsk.logical_id[2]
665 if tcp_port not in ports:
667 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
668 # gather network port reservation
669 net_port = getattr(instance, "network_port", None)
670 if net_port is not None:
671 if net_port not in ports:
673 ports[net_port].append((instance.name, "network port"))
675 # instance disk verify
676 for idx, disk in enumerate(instance.disks):
677 result.extend(["instance '%s' disk %d error: %s" %
678 (instance.name, idx, msg) for msg in disk.Verify()])
679 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
681 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
683 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
684 (idx, exp_name, actual_name))
685 for (idx, exp_name, actual_name) in wrong_names)
687 result.append("Instance '%s' has wrongly named disks: %s" %
688 (instance.name, tmp))
690 # cluster-wide pool of free ports
691 for free_port in cluster.tcpudp_port_pool:
692 if free_port not in ports:
693 ports[free_port] = []
694 ports[free_port].append(("cluster", "port marked as free"))
696 # compute tcp/udp duplicate ports
702 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
703 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
705 # highest used tcp port check
707 if keys[-1] > cluster.highest_used_port:
708 result.append("Highest used port mismatch, saved %s, computed %s" %
709 (cluster.highest_used_port, keys[-1]))
711 if not data.nodes[cluster.master_node].master_candidate:
712 result.append("Master node is not a master candidate")
714 # master candidate checks
715 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
717 result.append("Not enough master candidates: actual %d, target %d" %
721 for node_name, node in data.nodes.items():
722 if node.name != node_name:
723 result.append("Node '%s' is indexed by wrong name '%s'" %
724 (node.name, node_name))
725 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
726 result.append("Node %s state is invalid: master_candidate=%s,"
727 " drain=%s, offline=%s" %
728 (node.name, node.master_candidate, node.drained,
730 if node.group not in data.nodegroups:
731 result.append("Node '%s' has invalid group '%s'" %
732 (node.name, node.group))
734 _helper("node %s" % node.name, "ndparams",
735 cluster.FillND(node, data.nodegroups[node.group]),
736 constants.NDS_PARAMETER_TYPES)
739 nodegroups_names = set()
740 for nodegroup_uuid in data.nodegroups:
741 nodegroup = data.nodegroups[nodegroup_uuid]
742 if nodegroup.uuid != nodegroup_uuid:
743 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
744 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
745 if utils.UUID_RE.match(nodegroup.name.lower()):
746 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
747 (nodegroup.name, nodegroup.uuid))
748 if nodegroup.name in nodegroups_names:
749 result.append("duplicate node group name '%s'" % nodegroup.name)
751 nodegroups_names.add(nodegroup.name)
752 group_name = "group %s" % nodegroup.name
753 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
755 _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
756 if nodegroup.ndparams:
757 _helper(group_name, "ndparams",
758 cluster.SimpleFillND(nodegroup.ndparams),
759 constants.NDS_PARAMETER_TYPES)
762 _, duplicates = self._UnlockedComputeDRBDMap()
763 for node, minor, instance_a, instance_b in duplicates:
764 result.append("DRBD minor %d on node %s is assigned twice to instances"
765 " %s and %s" % (minor, node, instance_a, instance_b))
768 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
771 def _AddIpAddress(ip, name):
772 ips.setdefault(ip, []).append(name)
774 _AddIpAddress(cluster.master_ip, "cluster_ip")
776 for node in data.nodes.values():
777 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
778 if node.secondary_ip != node.primary_ip:
779 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
781 for instance in data.instances.values():
782 for idx, nic in enumerate(instance.nics):
786 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
787 nic_mode = nicparams[constants.NIC_MODE]
788 nic_link = nicparams[constants.NIC_LINK]
790 if nic_mode == constants.NIC_MODE_BRIDGED:
791 link = "bridge:%s" % nic_link
792 elif nic_mode == constants.NIC_MODE_ROUTED:
793 link = "route:%s" % nic_link
795 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
797 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
798 "instance:%s/nic:%d" % (instance.name, idx))
800 for ip, owners in ips.items():
802 result.append("IP address %s is used by multiple owners: %s" %
803 (ip, utils.CommaJoin(owners)))
807 @locking.ssynchronized(_config_lock, shared=1)
808 def VerifyConfig(self):
811 This is just a wrapper over L{_UnlockedVerifyConfig}.
814 @return: a list of error messages; a non-empty list signifies
818 return self._UnlockedVerifyConfig()
820 def _UnlockedSetDiskID(self, disk, node_name):
821 """Convert the unique ID to the ID needed on the target nodes.
823 This is used only for drbd, which needs ip/port configuration.
825 The routine descends down and updates its children also, because
826 this helps when the only the top device is passed to the remote
829 This function is for internal use, when the config lock is already held.
833 for child in disk.children:
834 self._UnlockedSetDiskID(child, node_name)
836 if disk.logical_id is None and disk.physical_id is not None:
838 if disk.dev_type == constants.LD_DRBD8:
839 pnode, snode, port, pminor, sminor, secret = disk.logical_id
840 if node_name not in (pnode, snode):
841 raise errors.ConfigurationError("DRBD device not knowing node %s" %
843 pnode_info = self._UnlockedGetNodeInfo(pnode)
844 snode_info = self._UnlockedGetNodeInfo(snode)
845 if pnode_info is None or snode_info is None:
846 raise errors.ConfigurationError("Can't find primary or secondary node"
847 " for %s" % str(disk))
848 p_data = (pnode_info.secondary_ip, port)
849 s_data = (snode_info.secondary_ip, port)
850 if pnode == node_name:
851 disk.physical_id = p_data + s_data + (pminor, secret)
852 else: # it must be secondary, we tested above
853 disk.physical_id = s_data + p_data + (sminor, secret)
855 disk.physical_id = disk.logical_id
858 @locking.ssynchronized(_config_lock)
859 def SetDiskID(self, disk, node_name):
860 """Convert the unique ID to the ID needed on the target nodes.
862 This is used only for drbd, which needs ip/port configuration.
864 The routine descends down and updates its children also, because
865 this helps when the only the top device is passed to the remote
869 return self._UnlockedSetDiskID(disk, node_name)
871 @locking.ssynchronized(_config_lock)
872 def AddTcpUdpPort(self, port):
873 """Adds a new port to the available port pool.
875 @warning: this method does not "flush" the configuration (via
876 L{_WriteConfig}); callers should do that themselves once the
877 configuration is stable
880 if not isinstance(port, int):
881 raise errors.ProgrammerError("Invalid type passed for port")
883 self._config_data.cluster.tcpudp_port_pool.add(port)
885 @locking.ssynchronized(_config_lock, shared=1)
886 def GetPortList(self):
887 """Returns a copy of the current port list.
890 return self._config_data.cluster.tcpudp_port_pool.copy()
892 @locking.ssynchronized(_config_lock)
893 def AllocatePort(self):
896 The port will be taken from the available port pool or from the
897 default port range (and in this case we increase
901 # If there are TCP/IP ports configured, we use them first.
902 if self._config_data.cluster.tcpudp_port_pool:
903 port = self._config_data.cluster.tcpudp_port_pool.pop()
905 port = self._config_data.cluster.highest_used_port + 1
906 if port >= constants.LAST_DRBD_PORT:
907 raise errors.ConfigurationError("The highest used port is greater"
908 " than %s. Aborting." %
909 constants.LAST_DRBD_PORT)
910 self._config_data.cluster.highest_used_port = port
915 def _UnlockedComputeDRBDMap(self):
916 """Compute the used DRBD minor/nodes.
919 @return: dictionary of node_name: dict of minor: instance_name;
920 the returned dict will have all the nodes in it (even if with
921 an empty list), and a list of duplicates; if the duplicates
922 list is not empty, the configuration is corrupted and its caller
923 should raise an exception
926 def _AppendUsedPorts(instance_name, disk, used):
928 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
929 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
930 for node, port in ((node_a, minor_a), (node_b, minor_b)):
931 assert node in used, ("Node '%s' of instance '%s' not found"
932 " in node list" % (node, instance_name))
933 if port in used[node]:
934 duplicates.append((node, port, instance_name, used[node][port]))
936 used[node][port] = instance_name
938 for child in disk.children:
939 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
943 my_dict = dict((node, {}) for node in self._config_data.nodes)
944 for instance in self._config_data.instances.itervalues():
945 for disk in instance.disks:
946 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
947 for (node, minor), instance in self._temporary_drbds.iteritems():
948 if minor in my_dict[node] and my_dict[node][minor] != instance:
949 duplicates.append((node, minor, instance, my_dict[node][minor]))
951 my_dict[node][minor] = instance
952 return my_dict, duplicates
954 @locking.ssynchronized(_config_lock)
955 def ComputeDRBDMap(self):
956 """Compute the used DRBD minor/nodes.
958 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
960 @return: dictionary of node_name: dict of minor: instance_name;
961 the returned dict will have all the nodes in it (even if with
965 d_map, duplicates = self._UnlockedComputeDRBDMap()
967 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
971 @locking.ssynchronized(_config_lock)
972 def AllocateDRBDMinor(self, nodes, instance):
973 """Allocate a drbd minor.
975 The free minor will be automatically computed from the existing
976 devices. A node can be given multiple times in order to allocate
977 multiple minors. The result is the list of minors, in the same
978 order as the passed nodes.
980 @type instance: string
981 @param instance: the instance for which we allocate minors
984 assert isinstance(instance, basestring), \
985 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
987 d_map, duplicates = self._UnlockedComputeDRBDMap()
989 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
995 # no minors used, we can start at 0
998 self._temporary_drbds[(nname, 0)] = instance
1002 ffree = utils.FirstFree(keys)
1004 # return the next minor
1005 # TODO: implement high-limit check
1006 minor = keys[-1] + 1
1009 # double-check minor against current instances
1010 assert minor not in d_map[nname], \
1011 ("Attempt to reuse allocated DRBD minor %d on node %s,"
1012 " already allocated to instance %s" %
1013 (minor, nname, d_map[nname][minor]))
1014 ndata[minor] = instance
1015 # double-check minor against reservation
1016 r_key = (nname, minor)
1017 assert r_key not in self._temporary_drbds, \
1018 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1019 " reserved for instance %s" %
1020 (minor, nname, self._temporary_drbds[r_key]))
1021 self._temporary_drbds[r_key] = instance
1022 result.append(minor)
1023 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1027 def _UnlockedReleaseDRBDMinors(self, instance):
1028 """Release temporary drbd minors allocated for a given instance.
1030 @type instance: string
1031 @param instance: the instance for which temporary minors should be
1035 assert isinstance(instance, basestring), \
1036 "Invalid argument passed to ReleaseDRBDMinors"
1037 for key, name in self._temporary_drbds.items():
1038 if name == instance:
1039 del self._temporary_drbds[key]
1041 @locking.ssynchronized(_config_lock)
1042 def ReleaseDRBDMinors(self, instance):
1043 """Release temporary drbd minors allocated for a given instance.
1045 This should be called on the error paths, on the success paths
1046 it's automatically called by the ConfigWriter add and update
1049 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1051 @type instance: string
1052 @param instance: the instance for which temporary minors should be
1056 self._UnlockedReleaseDRBDMinors(instance)
1058 @locking.ssynchronized(_config_lock, shared=1)
1059 def GetConfigVersion(self):
1060 """Get the configuration version.
1062 @return: Config version
1065 return self._config_data.version
1067 @locking.ssynchronized(_config_lock, shared=1)
1068 def GetClusterName(self):
1069 """Get cluster name.
1071 @return: Cluster name
1074 return self._config_data.cluster.cluster_name
1076 @locking.ssynchronized(_config_lock, shared=1)
1077 def GetMasterNode(self):
1078 """Get the hostname of the master node for this cluster.
1080 @return: Master hostname
1083 return self._config_data.cluster.master_node
1085 @locking.ssynchronized(_config_lock, shared=1)
1086 def GetMasterIP(self):
1087 """Get the IP of the master node for this cluster.
1092 return self._config_data.cluster.master_ip
1094 @locking.ssynchronized(_config_lock, shared=1)
1095 def GetMasterNetdev(self):
1096 """Get the master network device for this cluster.
1099 return self._config_data.cluster.master_netdev
1101 @locking.ssynchronized(_config_lock, shared=1)
1102 def GetMasterNetmask(self):
1103 """Get the netmask of the master node for this cluster.
1106 return self._config_data.cluster.master_netmask
1108 @locking.ssynchronized(_config_lock, shared=1)
1109 def GetUseExternalMipScript(self):
1110 """Get flag representing whether to use the external master IP setup script.
1113 return self._config_data.cluster.use_external_mip_script
1115 @locking.ssynchronized(_config_lock, shared=1)
1116 def GetFileStorageDir(self):
1117 """Get the file storage dir for this cluster.
1120 return self._config_data.cluster.file_storage_dir
1122 @locking.ssynchronized(_config_lock, shared=1)
1123 def GetSharedFileStorageDir(self):
1124 """Get the shared file storage dir for this cluster.
1127 return self._config_data.cluster.shared_file_storage_dir
1129 @locking.ssynchronized(_config_lock, shared=1)
1130 def GetHypervisorType(self):
1131 """Get the hypervisor type for this cluster.
1134 return self._config_data.cluster.enabled_hypervisors[0]
1136 @locking.ssynchronized(_config_lock, shared=1)
1137 def GetHostKey(self):
1138 """Return the rsa hostkey from the config.
1141 @return: the rsa hostkey
1144 return self._config_data.cluster.rsahostkeypub
1146 @locking.ssynchronized(_config_lock, shared=1)
1147 def GetDefaultIAllocator(self):
1148 """Get the default instance allocator for this cluster.
1151 return self._config_data.cluster.default_iallocator
1153 @locking.ssynchronized(_config_lock, shared=1)
1154 def GetPrimaryIPFamily(self):
1155 """Get cluster primary ip family.
1157 @return: primary ip family
1160 return self._config_data.cluster.primary_ip_family
1162 @locking.ssynchronized(_config_lock, shared=1)
1163 def GetMasterNetworkParameters(self):
1164 """Get network parameters of the master node.
1166 @rtype: L{object.MasterNetworkParameters}
1167 @return: network parameters of the master node
1170 cluster = self._config_data.cluster
1171 result = objects.MasterNetworkParameters(
1172 name=cluster.master_node, ip=cluster.master_ip,
1173 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1174 ip_family=cluster.primary_ip_family)
1178 @locking.ssynchronized(_config_lock)
1179 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1180 """Add a node group to the configuration.
1182 This method calls group.UpgradeConfig() to fill any missing attributes
1183 according to their default values.
1185 @type group: L{objects.NodeGroup}
1186 @param group: the NodeGroup object to add
1188 @param ec_id: unique id for the job to use when creating a missing UUID
1189 @type check_uuid: bool
1190 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1191 it does, ensure that it does not exist in the
1192 configuration already
1195 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1198 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1199 """Add a node group to the configuration.
1202 logging.info("Adding node group %s to configuration", group.name)
1204 # Some code might need to add a node group with a pre-populated UUID
1205 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1206 # the "does this UUID" exist already check.
1208 self._EnsureUUID(group, ec_id)
1211 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1212 except errors.OpPrereqError:
1215 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1216 " node group (UUID: %s)" %
1217 (group.name, existing_uuid),
1218 errors.ECODE_EXISTS)
1221 group.ctime = group.mtime = time.time()
1222 group.UpgradeConfig()
1224 self._config_data.nodegroups[group.uuid] = group
1225 self._config_data.cluster.serial_no += 1
1227 @locking.ssynchronized(_config_lock)
1228 def RemoveNodeGroup(self, group_uuid):
1229 """Remove a node group from the configuration.
1231 @type group_uuid: string
1232 @param group_uuid: the UUID of the node group to remove
1235 logging.info("Removing node group %s from configuration", group_uuid)
1237 if group_uuid not in self._config_data.nodegroups:
1238 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1240 assert len(self._config_data.nodegroups) != 1, \
1241 "Group '%s' is the only group, cannot be removed" % group_uuid
1243 del self._config_data.nodegroups[group_uuid]
1244 self._config_data.cluster.serial_no += 1
1247 def _UnlockedLookupNodeGroup(self, target):
1248 """Lookup a node group's UUID.
1250 @type target: string or None
1251 @param target: group name or UUID or None to look for the default
1253 @return: nodegroup UUID
1254 @raises errors.OpPrereqError: when the target group cannot be found
1258 if len(self._config_data.nodegroups) != 1:
1259 raise errors.OpPrereqError("More than one node group exists. Target"
1260 " group must be specified explicitly.")
1262 return self._config_data.nodegroups.keys()[0]
1263 if target in self._config_data.nodegroups:
1265 for nodegroup in self._config_data.nodegroups.values():
1266 if nodegroup.name == target:
1267 return nodegroup.uuid
1268 raise errors.OpPrereqError("Node group '%s' not found" % target,
1271 @locking.ssynchronized(_config_lock, shared=1)
1272 def LookupNodeGroup(self, target):
1273 """Lookup a node group's UUID.
1275 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1277 @type target: string or None
1278 @param target: group name or UUID or None to look for the default
1280 @return: nodegroup UUID
1283 return self._UnlockedLookupNodeGroup(target)
1285 def _UnlockedGetNodeGroup(self, uuid):
1286 """Lookup a node group.
1289 @param uuid: group UUID
1290 @rtype: L{objects.NodeGroup} or None
1291 @return: nodegroup object, or None if not found
1294 if uuid not in self._config_data.nodegroups:
1297 return self._config_data.nodegroups[uuid]
1299 @locking.ssynchronized(_config_lock, shared=1)
1300 def GetNodeGroup(self, uuid):
1301 """Lookup a node group.
1304 @param uuid: group UUID
1305 @rtype: L{objects.NodeGroup} or None
1306 @return: nodegroup object, or None if not found
1309 return self._UnlockedGetNodeGroup(uuid)
1311 @locking.ssynchronized(_config_lock, shared=1)
1312 def GetAllNodeGroupsInfo(self):
1313 """Get the configuration of all node groups.
1316 return dict(self._config_data.nodegroups)
1318 @locking.ssynchronized(_config_lock, shared=1)
1319 def GetNodeGroupList(self):
1320 """Get a list of node groups.
1323 return self._config_data.nodegroups.keys()
1325 @locking.ssynchronized(_config_lock, shared=1)
1326 def GetNodeGroupMembersByNodes(self, nodes):
1327 """Get nodes which are member in the same nodegroups as the given nodes.
1330 ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1331 return frozenset(member_name
1332 for node_name in nodes
1334 self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1336 @locking.ssynchronized(_config_lock, shared=1)
1337 def GetMultiNodeGroupInfo(self, group_uuids):
1338 """Get the configuration of multiple node groups.
1340 @param group_uuids: List of node group UUIDs
1342 @return: List of tuples of (group_uuid, group_info)
1345 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1347 @locking.ssynchronized(_config_lock)
1348 def AddInstance(self, instance, ec_id):
1349 """Add an instance to the config.
1351 This should be used after creating a new instance.
1353 @type instance: L{objects.Instance}
1354 @param instance: the instance object
1357 if not isinstance(instance, objects.Instance):
1358 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1360 if instance.disk_template != constants.DT_DISKLESS:
1361 all_lvs = instance.MapLVsByNode()
1362 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1364 all_macs = self._AllMACs()
1365 for nic in instance.nics:
1366 if nic.mac in all_macs:
1367 raise errors.ConfigurationError("Cannot add instance %s:"
1368 " MAC address '%s' already in use." %
1369 (instance.name, nic.mac))
1371 self._EnsureUUID(instance, ec_id)
1373 instance.serial_no = 1
1374 instance.ctime = instance.mtime = time.time()
1375 self._config_data.instances[instance.name] = instance
1376 self._config_data.cluster.serial_no += 1
1377 self._UnlockedReleaseDRBDMinors(instance.name)
1378 self._UnlockedCommitTemporaryIps(ec_id)
1381 def _EnsureUUID(self, item, ec_id):
1382 """Ensures a given object has a valid UUID.
1384 @param item: the instance or node to be checked
1385 @param ec_id: the execution context id for the uuid reservation
1389 item.uuid = self._GenerateUniqueID(ec_id)
1390 elif item.uuid in self._AllIDs(include_temporary=True):
1391 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1392 " in use" % (item.name, item.uuid))
1394 def _SetInstanceStatus(self, instance_name, status):
1395 """Set the instance's status to a given value.
1398 assert status in constants.ADMINST_ALL, \
1399 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1401 if instance_name not in self._config_data.instances:
1402 raise errors.ConfigurationError("Unknown instance '%s'" %
1404 instance = self._config_data.instances[instance_name]
1405 if instance.admin_state != status:
1406 instance.admin_state = status
1407 instance.serial_no += 1
1408 instance.mtime = time.time()
1411 @locking.ssynchronized(_config_lock)
1412 def MarkInstanceUp(self, instance_name):
1413 """Mark the instance status to up in the config.
1416 self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1418 @locking.ssynchronized(_config_lock)
1419 def MarkInstanceOffline(self, instance_name):
1420 """Mark the instance status to down in the config.
1423 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1425 @locking.ssynchronized(_config_lock)
1426 def RemoveInstance(self, instance_name):
1427 """Remove the instance from the configuration.
1430 if instance_name not in self._config_data.instances:
1431 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1433 # If a network port has been allocated to the instance,
1434 # return it to the pool of free ports.
1435 inst = self._config_data.instances[instance_name]
1436 network_port = getattr(inst, "network_port", None)
1437 if network_port is not None:
1438 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1440 instance = self._UnlockedGetInstanceInfo(instance_name)
1442 for nic in instance.nics:
1443 if nic.network is not None and nic.ip is not None:
1444 net_uuid = self._UnlockedLookupNetwork(nic.network)
1446 # Return all IP addresses to the respective address pools
1447 self._UnlockedCommitIp('release', net_uuid, nic.ip)
1450 del self._config_data.instances[instance_name]
1451 self._config_data.cluster.serial_no += 1
1454 @locking.ssynchronized(_config_lock)
1455 def RenameInstance(self, old_name, new_name):
1456 """Rename an instance.
1458 This needs to be done in ConfigWriter and not by RemoveInstance
1459 combined with AddInstance as only we can guarantee an atomic
1463 if old_name not in self._config_data.instances:
1464 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1466 # Operate on a copy to not loose instance object in case of a failure
1467 inst = self._config_data.instances[old_name].Copy()
1468 inst.name = new_name
1470 for (idx, disk) in enumerate(inst.disks):
1471 if disk.dev_type == constants.LD_FILE:
1472 # rename the file paths in logical and physical id
1473 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1474 disk.logical_id = (disk.logical_id[0],
1475 utils.PathJoin(file_storage_dir, inst.name,
1477 disk.physical_id = disk.logical_id
1479 # Actually replace instance object
1480 del self._config_data.instances[old_name]
1481 self._config_data.instances[inst.name] = inst
1483 # Force update of ssconf files
1484 self._config_data.cluster.serial_no += 1
1488 @locking.ssynchronized(_config_lock)
1489 def MarkInstanceDown(self, instance_name):
1490 """Mark the status of an instance to down in the configuration.
1493 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1495 def _UnlockedGetInstanceList(self):
1496 """Get the list of instances.
1498 This function is for internal use, when the config lock is already held.
1501 return self._config_data.instances.keys()
1503 @locking.ssynchronized(_config_lock, shared=1)
1504 def GetInstanceList(self):
1505 """Get the list of instances.
1507 @return: array of instances, ex. ['instance2.example.com',
1508 'instance1.example.com']
1511 return self._UnlockedGetInstanceList()
1513 def ExpandInstanceName(self, short_name):
1514 """Attempt to expand an incomplete instance name.
1517 # Locking is done in L{ConfigWriter.GetInstanceList}
1518 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1520 def _UnlockedGetInstanceInfo(self, instance_name):
1521 """Returns information about an instance.
1523 This function is for internal use, when the config lock is already held.
1526 if instance_name not in self._config_data.instances:
1529 return self._config_data.instances[instance_name]
1531 @locking.ssynchronized(_config_lock, shared=1)
1532 def GetInstanceInfo(self, instance_name):
1533 """Returns information about an instance.
1535 It takes the information from the configuration file. Other information of
1536 an instance are taken from the live systems.
1538 @param instance_name: name of the instance, e.g.
1539 I{instance1.example.com}
1541 @rtype: L{objects.Instance}
1542 @return: the instance object
1545 return self._UnlockedGetInstanceInfo(instance_name)
1547 @locking.ssynchronized(_config_lock, shared=1)
1548 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1549 """Returns set of node group UUIDs for instance's nodes.
1554 instance = self._UnlockedGetInstanceInfo(instance_name)
1556 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1559 nodes = [instance.primary_node]
1561 nodes = instance.all_nodes
1563 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1564 for node_name in nodes)
1566 @locking.ssynchronized(_config_lock, shared=1)
1567 def GetMultiInstanceInfo(self, instances):
1568 """Get the configuration of multiple instances.
1570 @param instances: list of instance names
1572 @return: list of tuples (instance, instance_info), where
1573 instance_info is what would GetInstanceInfo return for the
1574 node, while keeping the original order
1577 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1579 @locking.ssynchronized(_config_lock, shared=1)
1580 def GetAllInstancesInfo(self):
1581 """Get the configuration of all instances.
1584 @return: dict of (instance, instance_info), where instance_info is what
1585 would GetInstanceInfo return for the node
1588 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1589 for instance in self._UnlockedGetInstanceList()])
1592 @locking.ssynchronized(_config_lock, shared=1)
1593 def GetInstancesInfoByFilter(self, filter_fn):
1594 """Get instance configuration with a filter.
1596 @type filter_fn: callable
1597 @param filter_fn: Filter function receiving instance object as parameter,
1598 returning boolean. Important: this function is called while the
1599 configuration locks is held. It must not do any complex work or call
1600 functions potentially leading to a deadlock. Ideally it doesn't call any
1601 other functions and just compares instance attributes.
1604 return dict((name, inst)
1605 for (name, inst) in self._config_data.instances.items()
1608 @locking.ssynchronized(_config_lock)
1609 def AddNode(self, node, ec_id):
1610 """Add a node to the configuration.
1612 @type node: L{objects.Node}
1613 @param node: a Node instance
1616 logging.info("Adding node %s to configuration", node.name)
1618 self._EnsureUUID(node, ec_id)
1621 node.ctime = node.mtime = time.time()
1622 self._UnlockedAddNodeToGroup(node.name, node.group)
1623 self._config_data.nodes[node.name] = node
1624 self._config_data.cluster.serial_no += 1
1627 @locking.ssynchronized(_config_lock)
1628 def RemoveNode(self, node_name):
1629 """Remove a node from the configuration.
1632 logging.info("Removing node %s from configuration", node_name)
1634 if node_name not in self._config_data.nodes:
1635 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1637 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1638 del self._config_data.nodes[node_name]
1639 self._config_data.cluster.serial_no += 1
1642 def ExpandNodeName(self, short_name):
1643 """Attempt to expand an incomplete node name.
1646 # Locking is done in L{ConfigWriter.GetNodeList}
1647 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1649 def _UnlockedGetNodeInfo(self, node_name):
1650 """Get the configuration of a node, as stored in the config.
1652 This function is for internal use, when the config lock is already
1655 @param node_name: the node name, e.g. I{node1.example.com}
1657 @rtype: L{objects.Node}
1658 @return: the node object
1661 if node_name not in self._config_data.nodes:
1664 return self._config_data.nodes[node_name]
1666 @locking.ssynchronized(_config_lock, shared=1)
1667 def GetNodeInfo(self, node_name):
1668 """Get the configuration of a node, as stored in the config.
1670 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1672 @param node_name: the node name, e.g. I{node1.example.com}
1674 @rtype: L{objects.Node}
1675 @return: the node object
1678 return self._UnlockedGetNodeInfo(node_name)
1680 @locking.ssynchronized(_config_lock, shared=1)
1681 def GetNodeInstances(self, node_name):
1682 """Get the instances of a node, as stored in the config.
1684 @param node_name: the node name, e.g. I{node1.example.com}
1686 @rtype: (list, list)
1687 @return: a tuple with two lists: the primary and the secondary instances
1692 for inst in self._config_data.instances.values():
1693 if inst.primary_node == node_name:
1694 pri.append(inst.name)
1695 if node_name in inst.secondary_nodes:
1696 sec.append(inst.name)
1699 @locking.ssynchronized(_config_lock, shared=1)
1700 def GetNodeGroupInstances(self, uuid, primary_only=False):
1701 """Get the instances of a node group.
1703 @param uuid: Node group UUID
1704 @param primary_only: Whether to only consider primary nodes
1706 @return: List of instance names in node group
1710 nodes_fn = lambda inst: [inst.primary_node]
1712 nodes_fn = lambda inst: inst.all_nodes
1714 return frozenset(inst.name
1715 for inst in self._config_data.instances.values()
1716 for node_name in nodes_fn(inst)
1717 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1719 def _UnlockedGetNodeList(self):
1720 """Return the list of nodes which are in the configuration.
1722 This function is for internal use, when the config lock is already
1728 return self._config_data.nodes.keys()
1730 @locking.ssynchronized(_config_lock, shared=1)
1731 def GetNodeList(self):
1732 """Return the list of nodes which are in the configuration.
1735 return self._UnlockedGetNodeList()
1737 def _UnlockedGetOnlineNodeList(self):
1738 """Return the list of nodes which are online.
1741 all_nodes = [self._UnlockedGetNodeInfo(node)
1742 for node in self._UnlockedGetNodeList()]
1743 return [node.name for node in all_nodes if not node.offline]
1745 @locking.ssynchronized(_config_lock, shared=1)
1746 def GetOnlineNodeList(self):
1747 """Return the list of nodes which are online.
1750 return self._UnlockedGetOnlineNodeList()
1752 @locking.ssynchronized(_config_lock, shared=1)
1753 def GetVmCapableNodeList(self):
1754 """Return the list of nodes which are not vm capable.
1757 all_nodes = [self._UnlockedGetNodeInfo(node)
1758 for node in self._UnlockedGetNodeList()]
1759 return [node.name for node in all_nodes if node.vm_capable]
1761 @locking.ssynchronized(_config_lock, shared=1)
1762 def GetNonVmCapableNodeList(self):
1763 """Return the list of nodes which are not vm capable.
1766 all_nodes = [self._UnlockedGetNodeInfo(node)
1767 for node in self._UnlockedGetNodeList()]
1768 return [node.name for node in all_nodes if not node.vm_capable]
1770 @locking.ssynchronized(_config_lock, shared=1)
1771 def GetMultiNodeInfo(self, nodes):
1772 """Get the configuration of multiple nodes.
1774 @param nodes: list of node names
1776 @return: list of tuples of (node, node_info), where node_info is
1777 what would GetNodeInfo return for the node, in the original
1781 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1783 @locking.ssynchronized(_config_lock, shared=1)
1784 def GetAllNodesInfo(self):
1785 """Get the configuration of all nodes.
1788 @return: dict of (node, node_info), where node_info is what
1789 would GetNodeInfo return for the node
1792 return self._UnlockedGetAllNodesInfo()
1794 def _UnlockedGetAllNodesInfo(self):
1795 """Gets configuration of all nodes.
1797 @note: See L{GetAllNodesInfo}
1800 return dict([(node, self._UnlockedGetNodeInfo(node))
1801 for node in self._UnlockedGetNodeList()])
1803 @locking.ssynchronized(_config_lock, shared=1)
1804 def GetNodeGroupsFromNodes(self, nodes):
1805 """Returns groups for a list of nodes.
1807 @type nodes: list of string
1808 @param nodes: List of node names
1812 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1814 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1815 """Get the number of current and maximum desired and possible candidates.
1817 @type exceptions: list
1818 @param exceptions: if passed, list of nodes that should be ignored
1820 @return: tuple of (current, desired and possible, possible)
1823 mc_now = mc_should = mc_max = 0
1824 for node in self._config_data.nodes.values():
1825 if exceptions and node.name in exceptions:
1827 if not (node.offline or node.drained) and node.master_capable:
1829 if node.master_candidate:
1831 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1832 return (mc_now, mc_should, mc_max)
1834 @locking.ssynchronized(_config_lock, shared=1)
1835 def GetMasterCandidateStats(self, exceptions=None):
1836 """Get the number of current and maximum possible candidates.
1838 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1840 @type exceptions: list
1841 @param exceptions: if passed, list of nodes that should be ignored
1843 @return: tuple of (current, max)
1846 return self._UnlockedGetMasterCandidateStats(exceptions)
1848 @locking.ssynchronized(_config_lock)
1849 def MaintainCandidatePool(self, exceptions):
1850 """Try to grow the candidate pool to the desired size.
1852 @type exceptions: list
1853 @param exceptions: if passed, list of nodes that should be ignored
1855 @return: list with the adjusted nodes (L{objects.Node} instances)
1858 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1861 node_list = self._config_data.nodes.keys()
1862 random.shuffle(node_list)
1863 for name in node_list:
1864 if mc_now >= mc_max:
1866 node = self._config_data.nodes[name]
1867 if (node.master_candidate or node.offline or node.drained or
1868 node.name in exceptions or not node.master_capable):
1870 mod_list.append(node)
1871 node.master_candidate = True
1874 if mc_now != mc_max:
1875 # this should not happen
1876 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1877 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1879 self._config_data.cluster.serial_no += 1
1884 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1885 """Add a given node to the specified group.
1888 if nodegroup_uuid not in self._config_data.nodegroups:
1889 # This can happen if a node group gets deleted between its lookup and
1890 # when we're adding the first node to it, since we don't keep a lock in
1891 # the meantime. It's ok though, as we'll fail cleanly if the node group
1892 # is not found anymore.
1893 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1894 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1895 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1897 def _UnlockedRemoveNodeFromGroup(self, node):
1898 """Remove a given node from its group.
1901 nodegroup = node.group
1902 if nodegroup not in self._config_data.nodegroups:
1903 logging.warning("Warning: node '%s' has unknown node group '%s'"
1904 " (while being removed from it)", node.name, nodegroup)
1905 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1906 if node.name not in nodegroup_obj.members:
1907 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1908 " (while being removed from it)", node.name, nodegroup)
1910 nodegroup_obj.members.remove(node.name)
1912 @locking.ssynchronized(_config_lock)
1913 def AssignGroupNodes(self, mods):
1914 """Changes the group of a number of nodes.
1916 @type mods: list of tuples; (node name, new group UUID)
1917 @param mods: Node membership modifications
1920 groups = self._config_data.nodegroups
1921 nodes = self._config_data.nodes
1925 # Try to resolve names/UUIDs first
1926 for (node_name, new_group_uuid) in mods:
1928 node = nodes[node_name]
1930 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1932 if node.group == new_group_uuid:
1933 # Node is being assigned to its current group
1934 logging.debug("Node '%s' was assigned to its current group (%s)",
1935 node_name, node.group)
1938 # Try to find current group of node
1940 old_group = groups[node.group]
1942 raise errors.ConfigurationError("Unable to find old group '%s'" %
1945 # Try to find new group for node
1947 new_group = groups[new_group_uuid]
1949 raise errors.ConfigurationError("Unable to find new group '%s'" %
1952 assert node.name in old_group.members, \
1953 ("Inconsistent configuration: node '%s' not listed in members for its"
1954 " old group '%s'" % (node.name, old_group.uuid))
1955 assert node.name not in new_group.members, \
1956 ("Inconsistent configuration: node '%s' already listed in members for"
1957 " its new group '%s'" % (node.name, new_group.uuid))
1959 resmod.append((node, old_group, new_group))
1962 for (node, old_group, new_group) in resmod:
1963 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1964 "Assigning to current group is not possible"
1966 node.group = new_group.uuid
1968 # Update members of involved groups
1969 if node.name in old_group.members:
1970 old_group.members.remove(node.name)
1971 if node.name not in new_group.members:
1972 new_group.members.append(node.name)
1974 # Update timestamps and serials (only once per node/group object)
1976 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
1980 # Force ssconf update
1981 self._config_data.cluster.serial_no += 1
1985 def _BumpSerialNo(self):
1986 """Bump up the serial number of the config.
1989 self._config_data.serial_no += 1
1990 self._config_data.mtime = time.time()
1992 def _AllUUIDObjects(self):
1993 """Returns all objects with uuid attributes.
1996 return (self._config_data.instances.values() +
1997 self._config_data.nodes.values() +
1998 self._config_data.nodegroups.values() +
1999 [self._config_data.cluster])
2001 def _OpenConfig(self, accept_foreign):
2002 """Read the config data from disk.
2005 raw_data = utils.ReadFile(self._cfg_file)
2008 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2009 except Exception, err:
2010 raise errors.ConfigurationError(err)
2012 # Make sure the configuration has the right version
2013 _ValidateConfig(data)
2015 if (not hasattr(data, "cluster") or
2016 not hasattr(data.cluster, "rsahostkeypub")):
2017 raise errors.ConfigurationError("Incomplete configuration"
2018 " (missing cluster.rsahostkeypub)")
2020 if data.cluster.master_node != self._my_hostname and not accept_foreign:
2021 msg = ("The configuration denotes node %s as master, while my"
2022 " hostname is %s; opening a foreign configuration is only"
2023 " possible in accept_foreign mode" %
2024 (data.cluster.master_node, self._my_hostname))
2025 raise errors.ConfigurationError(msg)
2027 # Upgrade configuration if needed
2028 data.UpgradeConfig()
2030 self._config_data = data
2031 # reset the last serial as -1 so that the next write will cause
2033 self._last_cluster_serial = -1
2035 # And finally run our (custom) config upgrade sequence
2036 self._UpgradeConfig()
2038 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2040 def _UpgradeConfig(self):
2041 """Run upgrade steps that cannot be done purely in the objects.
2043 This is because some data elements need uniqueness across the
2044 whole configuration, etc.
2046 @warning: this function will call L{_WriteConfig()}, but also
2047 L{DropECReservations} so it needs to be called only from a
2048 "safe" place (the constructor). If one wanted to call it with
2049 the lock held, a DropECReservationUnlocked would need to be
2050 created first, to avoid causing deadlock.
2054 for item in self._AllUUIDObjects():
2055 if item.uuid is None:
2056 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2058 if not self._config_data.nodegroups:
2059 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2060 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2062 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2064 for node in self._config_data.nodes.values():
2066 node.group = self.LookupNodeGroup(None)
2068 # This is technically *not* an upgrade, but needs to be done both when
2069 # nodegroups are being added, and upon normally loading the config,
2070 # because the members list of a node group is discarded upon
2071 # serializing/deserializing the object.
2072 self._UnlockedAddNodeToGroup(node.name, node.group)
2075 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2076 # only called at config init time, without the lock held
2077 self.DropECReservations(_UPGRADE_CONFIG_JID)
2079 def _DistributeConfig(self, feedback_fn):
2080 """Distribute the configuration to the other nodes.
2082 Currently, this only copies the configuration file. In the future,
2083 it could be used to encapsulate the 2/3-phase update mechanism.
2093 myhostname = self._my_hostname
2094 # we can skip checking whether _UnlockedGetNodeInfo returns None
2095 # since the node list comes from _UnlocketGetNodeList, and we are
2096 # called with the lock held, so no modifications should take place
2098 for node_name in self._UnlockedGetNodeList():
2099 if node_name == myhostname:
2101 node_info = self._UnlockedGetNodeInfo(node_name)
2102 if not node_info.master_candidate:
2104 node_list.append(node_info.name)
2105 addr_list.append(node_info.primary_ip)
2107 # TODO: Use dedicated resolver talking to config writer for name resolution
2109 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2110 for to_node, to_result in result.items():
2111 msg = to_result.fail_msg
2113 msg = ("Copy of file %s to node %s failed: %s" %
2114 (self._cfg_file, to_node, msg))
2124 def _WriteConfig(self, destination=None, feedback_fn=None):
2125 """Write the configuration data to persistent storage.
2128 assert feedback_fn is None or callable(feedback_fn)
2130 # Warn on config errors, but don't abort the save - the
2131 # configuration has already been modified, and we can't revert;
2132 # the best we can do is to warn the user and save as is, leaving
2133 # recovery to the user
2134 config_errors = self._UnlockedVerifyConfig()
2136 errmsg = ("Configuration data is not consistent: %s" %
2137 (utils.CommaJoin(config_errors)))
2138 logging.critical(errmsg)
2142 if destination is None:
2143 destination = self._cfg_file
2144 self._BumpSerialNo()
2145 txt = serializer.Dump(self._config_data.ToDict())
2147 getents = self._getents()
2149 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2150 close=False, gid=getents.confd_gid, mode=0640)
2151 except errors.LockError:
2152 raise errors.ConfigurationError("The configuration file has been"
2153 " modified since the last write, cannot"
2156 self._cfg_id = utils.GetFileID(fd=fd)
2160 self.write_count += 1
2162 # and redistribute the config file to master candidates
2163 self._DistributeConfig(feedback_fn)
2165 # Write ssconf files on all nodes (including locally)
2166 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2167 if not self._offline:
2168 result = self._GetRpc(None).call_write_ssconf_files(
2169 self._UnlockedGetOnlineNodeList(),
2170 self._UnlockedGetSsconfValues())
2172 for nname, nresu in result.items():
2173 msg = nresu.fail_msg
2175 errmsg = ("Error while uploading ssconf files to"
2176 " node %s: %s" % (nname, msg))
2177 logging.warning(errmsg)
2182 self._last_cluster_serial = self._config_data.cluster.serial_no
2184 def _UnlockedGetSsconfValues(self):
2185 """Return the values needed by ssconf.
2188 @return: a dictionary with keys the ssconf names and values their
2193 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2194 node_names = utils.NiceSort(self._UnlockedGetNodeList())
2195 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2196 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2197 for ninfo in node_info]
2198 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2199 for ninfo in node_info]
2201 instance_data = fn(instance_names)
2202 off_data = fn(node.name for node in node_info if node.offline)
2203 on_data = fn(node.name for node in node_info if not node.offline)
2204 mc_data = fn(node.name for node in node_info if node.master_candidate)
2205 mc_ips_data = fn(node.primary_ip for node in node_info
2206 if node.master_candidate)
2207 node_data = fn(node_names)
2208 node_pri_ips_data = fn(node_pri_ips)
2209 node_snd_ips_data = fn(node_snd_ips)
2211 cluster = self._config_data.cluster
2212 cluster_tags = fn(cluster.GetTags())
2214 hypervisor_list = fn(cluster.enabled_hypervisors)
2216 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2218 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2219 self._config_data.nodegroups.values()]
2220 nodegroups_data = fn(utils.NiceSort(nodegroups))
2221 networks = ["%s %s" % (net.uuid, net.name) for net in
2222 self._config_data.networks.values()]
2223 networks_data = fn(utils.NiceSort(networks))
2226 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2227 constants.SS_CLUSTER_TAGS: cluster_tags,
2228 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2229 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2230 constants.SS_MASTER_CANDIDATES: mc_data,
2231 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2232 constants.SS_MASTER_IP: cluster.master_ip,
2233 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2234 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2235 constants.SS_MASTER_NODE: cluster.master_node,
2236 constants.SS_NODE_LIST: node_data,
2237 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2238 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2239 constants.SS_OFFLINE_NODES: off_data,
2240 constants.SS_ONLINE_NODES: on_data,
2241 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2242 constants.SS_INSTANCE_LIST: instance_data,
2243 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2244 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2245 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2246 constants.SS_UID_POOL: uid_pool,
2247 constants.SS_NODEGROUPS: nodegroups_data,
2248 constants.SS_NETWORKS: networks_data,
2250 bad_values = [(k, v) for k, v in ssconf_values.items()
2251 if not isinstance(v, (str, basestring))]
2253 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2254 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2255 " values: %s" % err)
2256 return ssconf_values
2258 @locking.ssynchronized(_config_lock, shared=1)
2259 def GetSsconfValues(self):
2260 """Wrapper using lock around _UnlockedGetSsconf().
2263 return self._UnlockedGetSsconfValues()
2265 @locking.ssynchronized(_config_lock, shared=1)
2266 def GetVGName(self):
2267 """Return the volume group name.
2270 return self._config_data.cluster.volume_group_name
2272 @locking.ssynchronized(_config_lock)
2273 def SetVGName(self, vg_name):
2274 """Set the volume group name.
2277 self._config_data.cluster.volume_group_name = vg_name
2278 self._config_data.cluster.serial_no += 1
2281 @locking.ssynchronized(_config_lock, shared=1)
2282 def GetDRBDHelper(self):
2283 """Return DRBD usermode helper.
2286 return self._config_data.cluster.drbd_usermode_helper
2288 @locking.ssynchronized(_config_lock)
2289 def SetDRBDHelper(self, drbd_helper):
2290 """Set DRBD usermode helper.
2293 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2294 self._config_data.cluster.serial_no += 1
2297 @locking.ssynchronized(_config_lock, shared=1)
2298 def GetMACPrefix(self):
2299 """Return the mac prefix.
2302 return self._config_data.cluster.mac_prefix
2304 @locking.ssynchronized(_config_lock, shared=1)
2305 def GetClusterInfo(self):
2306 """Returns information about the cluster
2308 @rtype: L{objects.Cluster}
2309 @return: the cluster object
2312 return self._config_data.cluster
2314 @locking.ssynchronized(_config_lock, shared=1)
2315 def HasAnyDiskOfType(self, dev_type):
2316 """Check if in there is at disk of the given type in the configuration.
2319 return self._config_data.HasAnyDiskOfType(dev_type)
2321 @locking.ssynchronized(_config_lock)
2322 def Update(self, target, feedback_fn, ec_id=None):
2323 """Notify function to be called after updates.
2325 This function must be called when an object (as returned by
2326 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2327 caller wants the modifications saved to the backing store. Note
2328 that all modified objects will be saved, but the target argument
2329 is the one the caller wants to ensure that it's saved.
2331 @param target: an instance of either L{objects.Cluster},
2332 L{objects.Node} or L{objects.Instance} which is existing in
2334 @param feedback_fn: Callable feedback function
2337 if self._config_data is None:
2338 raise errors.ProgrammerError("Configuration file not read,"
2340 update_serial = False
2341 if isinstance(target, objects.Cluster):
2342 test = target == self._config_data.cluster
2343 elif isinstance(target, objects.Node):
2344 test = target in self._config_data.nodes.values()
2345 update_serial = True
2346 elif isinstance(target, objects.Instance):
2347 test = target in self._config_data.instances.values()
2348 elif isinstance(target, objects.NodeGroup):
2349 test = target in self._config_data.nodegroups.values()
2350 elif isinstance(target, objects.Network):
2351 test = target in self._config_data.networks.values()
2353 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2354 " ConfigWriter.Update" % type(target))
2356 raise errors.ConfigurationError("Configuration updated since object"
2357 " has been read or unknown object")
2358 target.serial_no += 1
2359 target.mtime = now = time.time()
2362 # for node updates, we need to increase the cluster serial too
2363 self._config_data.cluster.serial_no += 1
2364 self._config_data.cluster.mtime = now
2366 if isinstance(target, objects.Instance):
2367 self._UnlockedReleaseDRBDMinors(target.name)
2369 if ec_id is not None:
2370 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2371 self._UnlockedCommitTemporaryIps(ec_id)
2373 self._WriteConfig(feedback_fn=feedback_fn)
2375 @locking.ssynchronized(_config_lock)
2376 def DropECReservations(self, ec_id):
2377 """Drop per-execution-context reservations
2380 for rm in self._all_rms:
2381 rm.DropECReservations(ec_id)
2383 @locking.ssynchronized(_config_lock, shared=1)
2384 def GetAllNetworksInfo(self):
2385 """Get the configuration of all networks
2388 return dict(self._config_data.networks)
2390 def _UnlockedGetNetworkList(self):
2391 """Get the list of networks.
2393 This function is for internal use, when the config lock is already held.
2396 return self._config_data.networks.keys()
2398 @locking.ssynchronized(_config_lock, shared=1)
2399 def GetNetworkList(self):
2400 """Get the list of networks.
2402 @return: array of networks, ex. ["main", "vlan100", "200]
2405 return self._UnlockedGetNetworkList()
2407 @locking.ssynchronized(_config_lock, shared=1)
2408 def GetNetworkNames(self):
2409 """Get a list of network names
2412 names = [network.name
2413 for network in self._config_data.networks.values()]
2416 def _UnlockedGetNetwork(self, uuid):
2417 """Returns information about a network.
2419 This function is for internal use, when the config lock is already held.
2422 if uuid not in self._config_data.networks:
2425 return self._config_data.networks[uuid]
2427 @locking.ssynchronized(_config_lock, shared=1)
2428 def GetNetwork(self, uuid):
2429 """Returns information about a network.
2431 It takes the information from the configuration file.
2433 @param uuid: UUID of the network
2435 @rtype: L{objects.Network}
2436 @return: the network object
2439 return self._UnlockedGetNetwork(uuid)
2441 @locking.ssynchronized(_config_lock)
2442 def AddNetwork(self, net, ec_id, check_uuid=True):
2443 """Add a network to the configuration.
2445 @type net: L{objects.Network}
2446 @param net: the Network object to add
2448 @param ec_id: unique id for the job to use when creating a missing UUID
2451 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2454 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2455 """Add a network to the configuration.
2458 logging.info("Adding network %s to configuration", net.name)
2461 self._EnsureUUID(net, ec_id)
2463 existing_uuid = self._UnlockedLookupNetwork(net.name)
2465 raise errors.OpPrereqError("Desired network name '%s' already"
2466 " exists as a network (UUID: %s)" %
2467 (net.name, existing_uuid),
2468 errors.ECODE_EXISTS)
2470 self._config_data.networks[net.uuid] = net
2471 self._config_data.cluster.serial_no += 1
2473 def _UnlockedLookupNetwork(self, target):
2474 """Lookup a network's UUID.
2476 @type target: string
2477 @param target: network name or UUID
2479 @return: network UUID
2480 @raises errors.OpPrereqError: when the target network cannot be found
2483 if target in self._config_data.networks:
2485 for net in self._config_data.networks.values():
2486 if net.name == target:
2490 @locking.ssynchronized(_config_lock, shared=1)
2491 def LookupNetwork(self, target):
2492 """Lookup a network's UUID.
2494 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2496 @type target: string
2497 @param target: network name or UUID
2499 @return: network UUID
2502 return self._UnlockedLookupNetwork(target)
2504 @locking.ssynchronized(_config_lock)
2505 def RemoveNetwork(self, network_uuid):
2506 """Remove a network from the configuration.
2508 @type network_uuid: string
2509 @param network_uuid: the UUID of the network to remove
2512 logging.info("Removing network %s from configuration", network_uuid)
2514 if network_uuid not in self._config_data.networks:
2515 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2517 del self._config_data.networks[network_uuid]
2518 self._config_data.cluster.serial_no += 1
2521 def _UnlockedGetGroupNetParams(self, net, node):
2522 """Get the netparams (mode, link) of a network.
2524 Get a network's netparams for a given node.
2527 @param net: network name
2529 @param node: node name
2530 @rtype: dict or None
2534 net_uuid = self._UnlockedLookupNetwork(net)
2535 if net_uuid is None:
2538 node_info = self._UnlockedGetNodeInfo(node)
2539 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2540 netparams = nodegroup_info.networks.get(net_uuid, None)
2544 @locking.ssynchronized(_config_lock, shared=1)
2545 def GetGroupNetParams(self, net, node):
2546 """Locking wrapper of _UnlockedGetGroupNetParams()
2549 return self._UnlockedGetGroupNetParams(net, node)
2552 @locking.ssynchronized(_config_lock, shared=1)
2553 def CheckIPInNodeGroup(self, ip, node):
2554 """Check for conflictig IP.
2557 @param ip: ip address
2559 @param node: node name
2560 @rtype: (string, dict) or (None, None)
2561 @return: (network name, netparams)
2566 node_info = self._UnlockedGetNodeInfo(node)
2567 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2568 for net_uuid in nodegroup_info.networks.keys():
2569 net_info = self._UnlockedGetNetwork(net_uuid)
2570 pool = network.AddressPool(net_info)
2571 if pool._Contains(ip):
2572 return (net_info.name, nodegroup_info.networks[net_uuid])