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):
112 """ Used when you want to retrieve all reservations for a specific
113 execution context. E.g when commiting reserved IPs for a specific
118 if ec_id in self._ec_reserved:
119 ec_reserved.update(self._ec_reserved[ec_id])
122 def Generate(self, existing, generate_one_fn, ec_id):
123 """Generate a new resource of this type
126 assert callable(generate_one_fn)
128 all_elems = self.GetReserved()
129 all_elems.update(existing)
132 new_resource = generate_one_fn()
133 if new_resource is not None and new_resource not in all_elems:
136 raise errors.ConfigurationError("Not able generate new resource"
137 " (last tried: %s)" % new_resource)
138 self.Reserve(ec_id, new_resource)
142 def _MatchNameComponentIgnoreCase(short_name, names):
143 """Wrapper around L{utils.text.MatchNameComponent}.
146 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
149 def _CheckInstanceDiskIvNames(disks):
150 """Checks if instance's disks' C{iv_name} attributes are in order.
152 @type disks: list of L{objects.Disk}
153 @param disks: List of disks
154 @rtype: list of tuples; (int, string, string)
155 @return: List of wrongly named disks, each tuple contains disk index,
156 expected and actual name
161 for (idx, disk) in enumerate(disks):
162 exp_iv_name = "disk/%s" % idx
163 if disk.iv_name != exp_iv_name:
164 result.append((idx, exp_iv_name, disk.iv_name))
170 """The interface to the cluster configuration.
172 @ivar _temporary_lvs: reservation manager for temporary LVs
173 @ivar _all_rms: a list of all temporary reservation managers
176 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
177 accept_foreign=False):
179 self._lock = _config_lock
180 self._config_data = None
181 self._offline = offline
183 self._cfg_file = pathutils.CLUSTER_CONF_FILE
185 self._cfg_file = cfg_file
186 self._getents = _getents
187 self._temporary_ids = TemporaryReservationManager()
188 self._temporary_drbds = {}
189 self._temporary_macs = TemporaryReservationManager()
190 self._temporary_secrets = TemporaryReservationManager()
191 self._temporary_lvs = TemporaryReservationManager()
192 self._temporary_ips = TemporaryReservationManager()
193 self._all_rms = [self._temporary_ids, self._temporary_macs,
194 self._temporary_secrets, self._temporary_lvs,
196 # Note: in order to prevent errors when resolving our name in
197 # _DistributeConfig, we compute it here once and reuse it; it's
198 # better to raise an error before starting to modify the config
199 # file than after it was modified
200 self._my_hostname = netutils.Hostname.GetSysName()
201 self._last_cluster_serial = -1
204 self._OpenConfig(accept_foreign)
206 def _GetRpc(self, address_list):
207 """Returns RPC runner for configuration.
210 return rpc.ConfigRunner(self._context, address_list)
212 def SetContext(self, context):
213 """Sets Ganeti context.
216 self._context = context
218 # this method needs to be static, so that we can call it on the class
221 """Check if the cluster is configured.
224 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
226 @locking.ssynchronized(_config_lock, shared=1)
227 def GetNdParams(self, node):
228 """Get the node params populated with cluster defaults.
230 @type node: L{objects.Node}
231 @param node: The node we want to know the params for
232 @return: A dict with the filled in node params
235 nodegroup = self._UnlockedGetNodeGroup(node.group)
236 return self._config_data.cluster.FillND(node, nodegroup)
238 @locking.ssynchronized(_config_lock, shared=1)
239 def GetInstanceDiskParams(self, instance):
240 """Get the disk params populated with inherit chain.
242 @type instance: L{objects.Instance}
243 @param instance: The instance we want to know the params for
244 @return: A dict with the filled in disk params
247 node = self._UnlockedGetNodeInfo(instance.primary_node)
248 nodegroup = self._UnlockedGetNodeGroup(node.group)
249 return self._UnlockedGetGroupDiskParams(nodegroup)
251 @locking.ssynchronized(_config_lock, shared=1)
252 def GetGroupDiskParams(self, group):
253 """Get the disk params populated with inherit chain.
255 @type group: L{objects.NodeGroup}
256 @param group: The group we want to know the params for
257 @return: A dict with the filled in disk params
260 return self._UnlockedGetGroupDiskParams(group)
262 def _UnlockedGetGroupDiskParams(self, group):
263 """Get the disk params populated with inherit chain down to node-group.
265 @type group: L{objects.NodeGroup}
266 @param group: The group we want to know the params for
267 @return: A dict with the filled in disk params
270 return self._config_data.cluster.SimpleFillDP(group.diskparams)
272 def _UnlockedGetNetworkMACPrefix(self, net):
273 """Return the network mac prefix if it exists or the cluster level default.
278 net_uuid = self._UnlockedLookupNetwork(net)
280 nobj = self._UnlockedGetNetwork(net_uuid)
282 prefix = nobj.mac_prefix
286 def _GenerateOneMAC(self, prefix=None):
287 """Return a function that randomly generates a MAC suffic
288 and appends it to the given prefix. If prefix is not given get
289 the cluster level default.
293 prefix = self._config_data.cluster.mac_prefix
296 byte1 = random.randrange(0, 256)
297 byte2 = random.randrange(0, 256)
298 byte3 = random.randrange(0, 256)
299 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
304 @locking.ssynchronized(_config_lock, shared=1)
305 def GenerateMAC(self, net, ec_id):
306 """Generate a MAC for an instance.
308 This should check the current instances for duplicates.
311 existing = self._AllMACs()
312 prefix = self._UnlockedGetNetworkMACPrefix(net)
313 gen_mac = self._GenerateOneMAC(prefix)
314 return self._temporary_ids.Generate(existing, gen_mac, ec_id)
316 @locking.ssynchronized(_config_lock, shared=1)
317 def ReserveMAC(self, mac, ec_id):
318 """Reserve a MAC for an instance.
320 This only checks instances managed by this cluster, it does not
321 check for potential collisions elsewhere.
324 all_macs = self._AllMACs()
326 raise errors.ReservationError("mac already in use")
328 self._temporary_macs.Reserve(ec_id, mac)
330 def _UnlockedCommitTemporaryIps(self, ec_id):
331 """Commit all reserved IP address to their respective pools
334 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
335 self._UnlockedCommitIp(action, net_uuid, address)
337 def _UnlockedCommitIp(self, action, net_uuid, address):
338 """Commit a reserved IP address to an IP pool.
340 The IP address is taken from the network's IP pool and marked as reserved.
343 nobj = self._UnlockedGetNetwork(net_uuid)
344 pool = network.AddressPool(nobj)
345 if action == 'reserve':
346 pool.Reserve(address)
347 elif action == 'release':
348 pool.Release(address)
350 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
351 """Give a specific IP address back to an IP pool.
353 The IP address is returned to the IP pool designated by pool_id and marked
357 self._temporary_ips.Reserve(ec_id, ('release', address, net_uuid))
359 @locking.ssynchronized(_config_lock, shared=1)
360 def ReleaseIp(self, net, address, ec_id):
361 """Give a specified IP address back to an IP pool.
363 This is just a wrapper around _UnlockedReleaseIp.
366 net_uuid = self._UnlockedLookupNetwork(net)
368 self._UnlockedReleaseIp(net_uuid, address, ec_id)
370 @locking.ssynchronized(_config_lock, shared=1)
371 def GenerateIp(self, net, ec_id):
372 """Find a free IPv4 address for an instance.
375 net_uuid = self._UnlockedLookupNetwork(net)
376 nobj = self._UnlockedGetNetwork(net_uuid)
377 pool = network.AddressPool(nobj)
378 gen_free = pool.GenerateFree()
383 except StopIteration:
384 raise errors.ReservationError("Cannot generate IP. Network is full")
385 return ("reserve", ip, net_uuid)
387 _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
390 def _UnlockedReserveIp(self, net_uuid, address, ec_id):
391 """Reserve a given IPv4 address for use by an instance.
394 nobj = self._UnlockedGetNetwork(net_uuid)
395 pool = network.AddressPool(nobj)
397 isreserved = pool.IsReserved(address)
398 except errors.AddressPoolError:
399 raise errors.ReservationError("IP address not in network")
401 raise errors.ReservationError("IP address already in use")
403 return self._temporary_ips.Reserve(ec_id, ('reserve', address, net_uuid))
405 @locking.ssynchronized(_config_lock, shared=1)
406 def ReserveIp(self, net, address, ec_id):
407 """Reserve a given IPv4 address for use by an instance.
410 net_uuid = self._UnlockedLookupNetwork(net)
412 return self._UnlockedReserveIp(net_uuid, address, ec_id)
414 @locking.ssynchronized(_config_lock, shared=1)
415 def ReserveLV(self, lv_name, ec_id):
416 """Reserve an VG/LV pair for an instance.
418 @type lv_name: string
419 @param lv_name: the logical volume name to reserve
422 all_lvs = self._AllLVs()
423 if lv_name in all_lvs:
424 raise errors.ReservationError("LV already in use")
426 self._temporary_lvs.Reserve(ec_id, lv_name)
428 @locking.ssynchronized(_config_lock, shared=1)
429 def GenerateDRBDSecret(self, ec_id):
430 """Generate a DRBD secret.
432 This checks the current disks for duplicates.
435 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
436 utils.GenerateSecret,
440 """Compute the list of all LVs.
444 for instance in self._config_data.instances.values():
445 node_data = instance.MapLVsByNode()
446 for lv_list in node_data.values():
447 lvnames.update(lv_list)
450 def _AllIDs(self, include_temporary):
451 """Compute the list of all UUIDs and names we have.
453 @type include_temporary: boolean
454 @param include_temporary: whether to include the _temporary_ids set
456 @return: a set of IDs
460 if include_temporary:
461 existing.update(self._temporary_ids.GetReserved())
462 existing.update(self._AllLVs())
463 existing.update(self._config_data.instances.keys())
464 existing.update(self._config_data.nodes.keys())
465 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
468 def _GenerateUniqueID(self, ec_id):
469 """Generate an unique UUID.
471 This checks the current node, instances and disk names for
475 @return: the unique id
478 existing = self._AllIDs(include_temporary=False)
479 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
481 @locking.ssynchronized(_config_lock, shared=1)
482 def GenerateUniqueID(self, ec_id):
483 """Generate an unique ID.
485 This is just a wrapper over the unlocked version.
488 @param ec_id: unique id for the job to reserve the id to
491 return self._GenerateUniqueID(ec_id)
494 """Return all MACs present in the config.
497 @return: the list of all MACs
501 for instance in self._config_data.instances.values():
502 for nic in instance.nics:
503 result.append(nic.mac)
507 def _AllDRBDSecrets(self):
508 """Return all DRBD secrets present in the config.
511 @return: the list of all DRBD secrets
514 def helper(disk, result):
515 """Recursively gather secrets from this disk."""
516 if disk.dev_type == constants.DT_DRBD8:
517 result.append(disk.logical_id[5])
519 for child in disk.children:
520 helper(child, result)
523 for instance in self._config_data.instances.values():
524 for disk in instance.disks:
529 def _CheckDiskIDs(self, disk, l_ids, p_ids):
530 """Compute duplicate disk IDs
532 @type disk: L{objects.Disk}
533 @param disk: the disk at which to start searching
535 @param l_ids: list of current logical ids
537 @param p_ids: list of current physical ids
539 @return: a list of error messages
543 if disk.logical_id is not None:
544 if disk.logical_id in l_ids:
545 result.append("duplicate logical id %s" % str(disk.logical_id))
547 l_ids.append(disk.logical_id)
548 if disk.physical_id is not None:
549 if disk.physical_id in p_ids:
550 result.append("duplicate physical id %s" % str(disk.physical_id))
552 p_ids.append(disk.physical_id)
555 for child in disk.children:
556 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
559 def _UnlockedVerifyConfig(self):
563 @return: a list of error messages; a non-empty list signifies
567 # pylint: disable=R0914
571 data = self._config_data
572 cluster = data.cluster
576 # global cluster checks
577 if not cluster.enabled_hypervisors:
578 result.append("enabled hypervisors list doesn't have any entries")
579 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
581 result.append("enabled hypervisors contains invalid entries: %s" %
583 missing_hvp = (set(cluster.enabled_hypervisors) -
584 set(cluster.hvparams.keys()))
586 result.append("hypervisor parameters missing for the enabled"
587 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
589 if cluster.master_node not in data.nodes:
590 result.append("cluster has invalid primary node '%s'" %
593 def _helper(owner, attr, value, template):
595 utils.ForceDictType(value, template)
596 except errors.GenericError, err:
597 result.append("%s has invalid %s: %s" % (owner, attr, err))
599 def _helper_nic(owner, params):
601 objects.NIC.CheckParameterSyntax(params)
602 except errors.ConfigurationError, err:
603 result.append("%s has invalid nicparams: %s" % (owner, err))
605 def _helper_ipolicy(owner, params, check_std):
607 objects.InstancePolicy.CheckParameterSyntax(params, check_std)
608 except errors.ConfigurationError, err:
609 result.append("%s has invalid instance policy: %s" % (owner, err))
611 def _helper_ispecs(owner, params):
612 for key, value in params.items():
613 if key in constants.IPOLICY_ISPECS:
614 fullkey = "ipolicy/" + key
615 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
617 # FIXME: assuming list type
618 if key in constants.IPOLICY_PARAMETERS:
622 if not isinstance(value, exp_type):
623 result.append("%s has invalid instance policy: for %s,"
624 " expecting %s, got %s" %
625 (owner, key, exp_type.__name__, type(value)))
627 # check cluster parameters
628 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
629 constants.BES_PARAMETER_TYPES)
630 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
631 constants.NICS_PARAMETER_TYPES)
632 _helper_nic("cluster", cluster.SimpleFillNIC({}))
633 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
634 constants.NDS_PARAMETER_TYPES)
635 _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
636 _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
638 # per-instance checks
639 for instance_name in data.instances:
640 instance = data.instances[instance_name]
641 if instance.name != instance_name:
642 result.append("instance '%s' is indexed by wrong name '%s'" %
643 (instance.name, instance_name))
644 if instance.primary_node not in data.nodes:
645 result.append("instance '%s' has invalid primary node '%s'" %
646 (instance_name, instance.primary_node))
647 for snode in instance.secondary_nodes:
648 if snode not in data.nodes:
649 result.append("instance '%s' has invalid secondary node '%s'" %
650 (instance_name, snode))
651 for idx, nic in enumerate(instance.nics):
652 if nic.mac in seen_macs:
653 result.append("instance '%s' has NIC %d mac %s duplicate" %
654 (instance_name, idx, nic.mac))
656 seen_macs.append(nic.mac)
658 filled = cluster.SimpleFillNIC(nic.nicparams)
659 owner = "instance %s nic %d" % (instance.name, idx)
660 _helper(owner, "nicparams",
661 filled, constants.NICS_PARAMETER_TYPES)
662 _helper_nic(owner, filled)
665 if instance.beparams:
666 _helper("instance %s" % instance.name, "beparams",
667 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
669 # gather the drbd ports for duplicate checks
670 for (idx, dsk) in enumerate(instance.disks):
671 if dsk.dev_type in constants.LDS_DRBD:
672 tcp_port = dsk.logical_id[2]
673 if tcp_port not in ports:
675 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
676 # gather network port reservation
677 net_port = getattr(instance, "network_port", None)
678 if net_port is not None:
679 if net_port not in ports:
681 ports[net_port].append((instance.name, "network port"))
683 # instance disk verify
684 for idx, disk in enumerate(instance.disks):
685 result.extend(["instance '%s' disk %d error: %s" %
686 (instance.name, idx, msg) for msg in disk.Verify()])
687 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
689 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
691 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
692 (idx, exp_name, actual_name))
693 for (idx, exp_name, actual_name) in wrong_names)
695 result.append("Instance '%s' has wrongly named disks: %s" %
696 (instance.name, tmp))
698 # cluster-wide pool of free ports
699 for free_port in cluster.tcpudp_port_pool:
700 if free_port not in ports:
701 ports[free_port] = []
702 ports[free_port].append(("cluster", "port marked as free"))
704 # compute tcp/udp duplicate ports
710 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
711 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
713 # highest used tcp port check
715 if keys[-1] > cluster.highest_used_port:
716 result.append("Highest used port mismatch, saved %s, computed %s" %
717 (cluster.highest_used_port, keys[-1]))
719 if not data.nodes[cluster.master_node].master_candidate:
720 result.append("Master node is not a master candidate")
722 # master candidate checks
723 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
725 result.append("Not enough master candidates: actual %d, target %d" %
729 for node_name, node in data.nodes.items():
730 if node.name != node_name:
731 result.append("Node '%s' is indexed by wrong name '%s'" %
732 (node.name, node_name))
733 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
734 result.append("Node %s state is invalid: master_candidate=%s,"
735 " drain=%s, offline=%s" %
736 (node.name, node.master_candidate, node.drained,
738 if node.group not in data.nodegroups:
739 result.append("Node '%s' has invalid group '%s'" %
740 (node.name, node.group))
742 _helper("node %s" % node.name, "ndparams",
743 cluster.FillND(node, data.nodegroups[node.group]),
744 constants.NDS_PARAMETER_TYPES)
747 nodegroups_names = set()
748 for nodegroup_uuid in data.nodegroups:
749 nodegroup = data.nodegroups[nodegroup_uuid]
750 if nodegroup.uuid != nodegroup_uuid:
751 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
752 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
753 if utils.UUID_RE.match(nodegroup.name.lower()):
754 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
755 (nodegroup.name, nodegroup.uuid))
756 if nodegroup.name in nodegroups_names:
757 result.append("duplicate node group name '%s'" % nodegroup.name)
759 nodegroups_names.add(nodegroup.name)
760 group_name = "group %s" % nodegroup.name
761 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
763 _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
764 if nodegroup.ndparams:
765 _helper(group_name, "ndparams",
766 cluster.SimpleFillND(nodegroup.ndparams),
767 constants.NDS_PARAMETER_TYPES)
770 _, duplicates = self._UnlockedComputeDRBDMap()
771 for node, minor, instance_a, instance_b in duplicates:
772 result.append("DRBD minor %d on node %s is assigned twice to instances"
773 " %s and %s" % (minor, node, instance_a, instance_b))
776 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
779 def _AddIpAddress(ip, name):
780 ips.setdefault(ip, []).append(name)
782 _AddIpAddress(cluster.master_ip, "cluster_ip")
784 for node in data.nodes.values():
785 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
786 if node.secondary_ip != node.primary_ip:
787 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
789 for instance in data.instances.values():
790 for idx, nic in enumerate(instance.nics):
794 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
795 nic_mode = nicparams[constants.NIC_MODE]
796 nic_link = nicparams[constants.NIC_LINK]
798 if nic_mode == constants.NIC_MODE_BRIDGED:
799 link = "bridge:%s" % nic_link
800 elif nic_mode == constants.NIC_MODE_ROUTED:
801 link = "route:%s" % nic_link
803 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
805 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
806 "instance:%s/nic:%d" % (instance.name, idx))
808 for ip, owners in ips.items():
810 result.append("IP address %s is used by multiple owners: %s" %
811 (ip, utils.CommaJoin(owners)))
815 @locking.ssynchronized(_config_lock, shared=1)
816 def VerifyConfig(self):
819 This is just a wrapper over L{_UnlockedVerifyConfig}.
822 @return: a list of error messages; a non-empty list signifies
826 return self._UnlockedVerifyConfig()
828 def _UnlockedSetDiskID(self, disk, node_name):
829 """Convert the unique ID to the ID needed on the target nodes.
831 This is used only for drbd, which needs ip/port configuration.
833 The routine descends down and updates its children also, because
834 this helps when the only the top device is passed to the remote
837 This function is for internal use, when the config lock is already held.
841 for child in disk.children:
842 self._UnlockedSetDiskID(child, node_name)
844 if disk.logical_id is None and disk.physical_id is not None:
846 if disk.dev_type == constants.LD_DRBD8:
847 pnode, snode, port, pminor, sminor, secret = disk.logical_id
848 if node_name not in (pnode, snode):
849 raise errors.ConfigurationError("DRBD device not knowing node %s" %
851 pnode_info = self._UnlockedGetNodeInfo(pnode)
852 snode_info = self._UnlockedGetNodeInfo(snode)
853 if pnode_info is None or snode_info is None:
854 raise errors.ConfigurationError("Can't find primary or secondary node"
855 " for %s" % str(disk))
856 p_data = (pnode_info.secondary_ip, port)
857 s_data = (snode_info.secondary_ip, port)
858 if pnode == node_name:
859 disk.physical_id = p_data + s_data + (pminor, secret)
860 else: # it must be secondary, we tested above
861 disk.physical_id = s_data + p_data + (sminor, secret)
863 disk.physical_id = disk.logical_id
866 @locking.ssynchronized(_config_lock)
867 def SetDiskID(self, disk, node_name):
868 """Convert the unique ID to the ID needed on the target nodes.
870 This is used only for drbd, which needs ip/port configuration.
872 The routine descends down and updates its children also, because
873 this helps when the only the top device is passed to the remote
877 return self._UnlockedSetDiskID(disk, node_name)
879 @locking.ssynchronized(_config_lock)
880 def AddTcpUdpPort(self, port):
881 """Adds a new port to the available port pool.
883 @warning: this method does not "flush" the configuration (via
884 L{_WriteConfig}); callers should do that themselves once the
885 configuration is stable
888 if not isinstance(port, int):
889 raise errors.ProgrammerError("Invalid type passed for port")
891 self._config_data.cluster.tcpudp_port_pool.add(port)
893 @locking.ssynchronized(_config_lock, shared=1)
894 def GetPortList(self):
895 """Returns a copy of the current port list.
898 return self._config_data.cluster.tcpudp_port_pool.copy()
900 @locking.ssynchronized(_config_lock)
901 def AllocatePort(self):
904 The port will be taken from the available port pool or from the
905 default port range (and in this case we increase
909 # If there are TCP/IP ports configured, we use them first.
910 if self._config_data.cluster.tcpudp_port_pool:
911 port = self._config_data.cluster.tcpudp_port_pool.pop()
913 port = self._config_data.cluster.highest_used_port + 1
914 if port >= constants.LAST_DRBD_PORT:
915 raise errors.ConfigurationError("The highest used port is greater"
916 " than %s. Aborting." %
917 constants.LAST_DRBD_PORT)
918 self._config_data.cluster.highest_used_port = port
923 def _UnlockedComputeDRBDMap(self):
924 """Compute the used DRBD minor/nodes.
927 @return: dictionary of node_name: dict of minor: instance_name;
928 the returned dict will have all the nodes in it (even if with
929 an empty list), and a list of duplicates; if the duplicates
930 list is not empty, the configuration is corrupted and its caller
931 should raise an exception
934 def _AppendUsedPorts(instance_name, disk, used):
936 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
937 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
938 for node, port in ((node_a, minor_a), (node_b, minor_b)):
939 assert node in used, ("Node '%s' of instance '%s' not found"
940 " in node list" % (node, instance_name))
941 if port in used[node]:
942 duplicates.append((node, port, instance_name, used[node][port]))
944 used[node][port] = instance_name
946 for child in disk.children:
947 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
951 my_dict = dict((node, {}) for node in self._config_data.nodes)
952 for instance in self._config_data.instances.itervalues():
953 for disk in instance.disks:
954 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
955 for (node, minor), instance in self._temporary_drbds.iteritems():
956 if minor in my_dict[node] and my_dict[node][minor] != instance:
957 duplicates.append((node, minor, instance, my_dict[node][minor]))
959 my_dict[node][minor] = instance
960 return my_dict, duplicates
962 @locking.ssynchronized(_config_lock)
963 def ComputeDRBDMap(self):
964 """Compute the used DRBD minor/nodes.
966 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
968 @return: dictionary of node_name: dict of minor: instance_name;
969 the returned dict will have all the nodes in it (even if with
973 d_map, duplicates = self._UnlockedComputeDRBDMap()
975 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
979 @locking.ssynchronized(_config_lock)
980 def AllocateDRBDMinor(self, nodes, instance):
981 """Allocate a drbd minor.
983 The free minor will be automatically computed from the existing
984 devices. A node can be given multiple times in order to allocate
985 multiple minors. The result is the list of minors, in the same
986 order as the passed nodes.
988 @type instance: string
989 @param instance: the instance for which we allocate minors
992 assert isinstance(instance, basestring), \
993 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
995 d_map, duplicates = self._UnlockedComputeDRBDMap()
997 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
1001 ndata = d_map[nname]
1003 # no minors used, we can start at 0
1006 self._temporary_drbds[(nname, 0)] = instance
1010 ffree = utils.FirstFree(keys)
1012 # return the next minor
1013 # TODO: implement high-limit check
1014 minor = keys[-1] + 1
1017 # double-check minor against current instances
1018 assert minor not in d_map[nname], \
1019 ("Attempt to reuse allocated DRBD minor %d on node %s,"
1020 " already allocated to instance %s" %
1021 (minor, nname, d_map[nname][minor]))
1022 ndata[minor] = instance
1023 # double-check minor against reservation
1024 r_key = (nname, minor)
1025 assert r_key not in self._temporary_drbds, \
1026 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1027 " reserved for instance %s" %
1028 (minor, nname, self._temporary_drbds[r_key]))
1029 self._temporary_drbds[r_key] = instance
1030 result.append(minor)
1031 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1035 def _UnlockedReleaseDRBDMinors(self, instance):
1036 """Release temporary drbd minors allocated for a given instance.
1038 @type instance: string
1039 @param instance: the instance for which temporary minors should be
1043 assert isinstance(instance, basestring), \
1044 "Invalid argument passed to ReleaseDRBDMinors"
1045 for key, name in self._temporary_drbds.items():
1046 if name == instance:
1047 del self._temporary_drbds[key]
1049 @locking.ssynchronized(_config_lock)
1050 def ReleaseDRBDMinors(self, instance):
1051 """Release temporary drbd minors allocated for a given instance.
1053 This should be called on the error paths, on the success paths
1054 it's automatically called by the ConfigWriter add and update
1057 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1059 @type instance: string
1060 @param instance: the instance for which temporary minors should be
1064 self._UnlockedReleaseDRBDMinors(instance)
1066 @locking.ssynchronized(_config_lock, shared=1)
1067 def GetConfigVersion(self):
1068 """Get the configuration version.
1070 @return: Config version
1073 return self._config_data.version
1075 @locking.ssynchronized(_config_lock, shared=1)
1076 def GetClusterName(self):
1077 """Get cluster name.
1079 @return: Cluster name
1082 return self._config_data.cluster.cluster_name
1084 @locking.ssynchronized(_config_lock, shared=1)
1085 def GetMasterNode(self):
1086 """Get the hostname of the master node for this cluster.
1088 @return: Master hostname
1091 return self._config_data.cluster.master_node
1093 @locking.ssynchronized(_config_lock, shared=1)
1094 def GetMasterIP(self):
1095 """Get the IP of the master node for this cluster.
1100 return self._config_data.cluster.master_ip
1102 @locking.ssynchronized(_config_lock, shared=1)
1103 def GetMasterNetdev(self):
1104 """Get the master network device for this cluster.
1107 return self._config_data.cluster.master_netdev
1109 @locking.ssynchronized(_config_lock, shared=1)
1110 def GetMasterNetmask(self):
1111 """Get the netmask of the master node for this cluster.
1114 return self._config_data.cluster.master_netmask
1116 @locking.ssynchronized(_config_lock, shared=1)
1117 def GetUseExternalMipScript(self):
1118 """Get flag representing whether to use the external master IP setup script.
1121 return self._config_data.cluster.use_external_mip_script
1123 @locking.ssynchronized(_config_lock, shared=1)
1124 def GetFileStorageDir(self):
1125 """Get the file storage dir for this cluster.
1128 return self._config_data.cluster.file_storage_dir
1130 @locking.ssynchronized(_config_lock, shared=1)
1131 def GetSharedFileStorageDir(self):
1132 """Get the shared file storage dir for this cluster.
1135 return self._config_data.cluster.shared_file_storage_dir
1137 @locking.ssynchronized(_config_lock, shared=1)
1138 def GetHypervisorType(self):
1139 """Get the hypervisor type for this cluster.
1142 return self._config_data.cluster.enabled_hypervisors[0]
1144 @locking.ssynchronized(_config_lock, shared=1)
1145 def GetHostKey(self):
1146 """Return the rsa hostkey from the config.
1149 @return: the rsa hostkey
1152 return self._config_data.cluster.rsahostkeypub
1154 @locking.ssynchronized(_config_lock, shared=1)
1155 def GetDefaultIAllocator(self):
1156 """Get the default instance allocator for this cluster.
1159 return self._config_data.cluster.default_iallocator
1161 @locking.ssynchronized(_config_lock, shared=1)
1162 def GetPrimaryIPFamily(self):
1163 """Get cluster primary ip family.
1165 @return: primary ip family
1168 return self._config_data.cluster.primary_ip_family
1170 @locking.ssynchronized(_config_lock, shared=1)
1171 def GetMasterNetworkParameters(self):
1172 """Get network parameters of the master node.
1174 @rtype: L{object.MasterNetworkParameters}
1175 @return: network parameters of the master node
1178 cluster = self._config_data.cluster
1179 result = objects.MasterNetworkParameters(
1180 name=cluster.master_node, ip=cluster.master_ip,
1181 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1182 ip_family=cluster.primary_ip_family)
1186 @locking.ssynchronized(_config_lock)
1187 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1188 """Add a node group to the configuration.
1190 This method calls group.UpgradeConfig() to fill any missing attributes
1191 according to their default values.
1193 @type group: L{objects.NodeGroup}
1194 @param group: the NodeGroup object to add
1196 @param ec_id: unique id for the job to use when creating a missing UUID
1197 @type check_uuid: bool
1198 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1199 it does, ensure that it does not exist in the
1200 configuration already
1203 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1206 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1207 """Add a node group to the configuration.
1210 logging.info("Adding node group %s to configuration", group.name)
1212 # Some code might need to add a node group with a pre-populated UUID
1213 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1214 # the "does this UUID" exist already check.
1216 self._EnsureUUID(group, ec_id)
1219 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1220 except errors.OpPrereqError:
1223 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1224 " node group (UUID: %s)" %
1225 (group.name, existing_uuid),
1226 errors.ECODE_EXISTS)
1229 group.ctime = group.mtime = time.time()
1230 group.UpgradeConfig()
1232 self._config_data.nodegroups[group.uuid] = group
1233 self._config_data.cluster.serial_no += 1
1235 @locking.ssynchronized(_config_lock)
1236 def RemoveNodeGroup(self, group_uuid):
1237 """Remove a node group from the configuration.
1239 @type group_uuid: string
1240 @param group_uuid: the UUID of the node group to remove
1243 logging.info("Removing node group %s from configuration", group_uuid)
1245 if group_uuid not in self._config_data.nodegroups:
1246 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1248 assert len(self._config_data.nodegroups) != 1, \
1249 "Group '%s' is the only group, cannot be removed" % group_uuid
1251 del self._config_data.nodegroups[group_uuid]
1252 self._config_data.cluster.serial_no += 1
1255 def _UnlockedLookupNodeGroup(self, target):
1256 """Lookup a node group's UUID.
1258 @type target: string or None
1259 @param target: group name or UUID or None to look for the default
1261 @return: nodegroup UUID
1262 @raises errors.OpPrereqError: when the target group cannot be found
1266 if len(self._config_data.nodegroups) != 1:
1267 raise errors.OpPrereqError("More than one node group exists. Target"
1268 " group must be specified explicitly.")
1270 return self._config_data.nodegroups.keys()[0]
1271 if target in self._config_data.nodegroups:
1273 for nodegroup in self._config_data.nodegroups.values():
1274 if nodegroup.name == target:
1275 return nodegroup.uuid
1276 raise errors.OpPrereqError("Node group '%s' not found" % target,
1279 @locking.ssynchronized(_config_lock, shared=1)
1280 def LookupNodeGroup(self, target):
1281 """Lookup a node group's UUID.
1283 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1285 @type target: string or None
1286 @param target: group name or UUID or None to look for the default
1288 @return: nodegroup UUID
1291 return self._UnlockedLookupNodeGroup(target)
1293 def _UnlockedGetNodeGroup(self, uuid):
1294 """Lookup a node group.
1297 @param uuid: group UUID
1298 @rtype: L{objects.NodeGroup} or None
1299 @return: nodegroup object, or None if not found
1302 if uuid not in self._config_data.nodegroups:
1305 return self._config_data.nodegroups[uuid]
1307 @locking.ssynchronized(_config_lock, shared=1)
1308 def GetNodeGroup(self, uuid):
1309 """Lookup a node group.
1312 @param uuid: group UUID
1313 @rtype: L{objects.NodeGroup} or None
1314 @return: nodegroup object, or None if not found
1317 return self._UnlockedGetNodeGroup(uuid)
1319 @locking.ssynchronized(_config_lock, shared=1)
1320 def GetAllNodeGroupsInfo(self):
1321 """Get the configuration of all node groups.
1324 return dict(self._config_data.nodegroups)
1326 @locking.ssynchronized(_config_lock, shared=1)
1327 def GetNodeGroupList(self):
1328 """Get a list of node groups.
1331 return self._config_data.nodegroups.keys()
1333 @locking.ssynchronized(_config_lock, shared=1)
1334 def GetNodeGroupMembersByNodes(self, nodes):
1335 """Get nodes which are member in the same nodegroups as the given nodes.
1338 ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1339 return frozenset(member_name
1340 for node_name in nodes
1342 self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1344 @locking.ssynchronized(_config_lock, shared=1)
1345 def GetMultiNodeGroupInfo(self, group_uuids):
1346 """Get the configuration of multiple node groups.
1348 @param group_uuids: List of node group UUIDs
1350 @return: List of tuples of (group_uuid, group_info)
1353 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1355 @locking.ssynchronized(_config_lock)
1356 def AddInstance(self, instance, ec_id):
1357 """Add an instance to the config.
1359 This should be used after creating a new instance.
1361 @type instance: L{objects.Instance}
1362 @param instance: the instance object
1365 if not isinstance(instance, objects.Instance):
1366 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1368 if instance.disk_template != constants.DT_DISKLESS:
1369 all_lvs = instance.MapLVsByNode()
1370 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1372 all_macs = self._AllMACs()
1373 for nic in instance.nics:
1374 if nic.mac in all_macs:
1375 raise errors.ConfigurationError("Cannot add instance %s:"
1376 " MAC address '%s' already in use." %
1377 (instance.name, nic.mac))
1379 self._EnsureUUID(instance, ec_id)
1381 instance.serial_no = 1
1382 instance.ctime = instance.mtime = time.time()
1383 self._config_data.instances[instance.name] = instance
1384 self._config_data.cluster.serial_no += 1
1385 self._UnlockedReleaseDRBDMinors(instance.name)
1386 self._UnlockedCommitTemporaryIps(ec_id)
1389 def _EnsureUUID(self, item, ec_id):
1390 """Ensures a given object has a valid UUID.
1392 @param item: the instance or node to be checked
1393 @param ec_id: the execution context id for the uuid reservation
1397 item.uuid = self._GenerateUniqueID(ec_id)
1398 elif item.uuid in self._AllIDs(include_temporary=True):
1399 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1400 " in use" % (item.name, item.uuid))
1402 def _SetInstanceStatus(self, instance_name, status):
1403 """Set the instance's status to a given value.
1406 assert status in constants.ADMINST_ALL, \
1407 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1409 if instance_name not in self._config_data.instances:
1410 raise errors.ConfigurationError("Unknown instance '%s'" %
1412 instance = self._config_data.instances[instance_name]
1413 if instance.admin_state != status:
1414 instance.admin_state = status
1415 instance.serial_no += 1
1416 instance.mtime = time.time()
1419 @locking.ssynchronized(_config_lock)
1420 def MarkInstanceUp(self, instance_name):
1421 """Mark the instance status to up in the config.
1424 self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1426 @locking.ssynchronized(_config_lock)
1427 def MarkInstanceOffline(self, instance_name):
1428 """Mark the instance status to down in the config.
1431 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1433 @locking.ssynchronized(_config_lock)
1434 def RemoveInstance(self, instance_name):
1435 """Remove the instance from the configuration.
1438 if instance_name not in self._config_data.instances:
1439 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1441 # If a network port has been allocated to the instance,
1442 # return it to the pool of free ports.
1443 inst = self._config_data.instances[instance_name]
1444 network_port = getattr(inst, "network_port", None)
1445 if network_port is not None:
1446 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1448 instance = self._UnlockedGetInstanceInfo(instance_name)
1450 for nic in instance.nics:
1451 if nic.network is not None and nic.ip is not None:
1452 net_uuid = self._UnlockedLookupNetwork(nic.network)
1454 # Return all IP addresses to the respective address pools
1455 self._UnlockedCommitIp('release', net_uuid, nic.ip)
1457 del self._config_data.instances[instance_name]
1458 self._config_data.cluster.serial_no += 1
1461 @locking.ssynchronized(_config_lock)
1462 def RenameInstance(self, old_name, new_name):
1463 """Rename an instance.
1465 This needs to be done in ConfigWriter and not by RemoveInstance
1466 combined with AddInstance as only we can guarantee an atomic
1470 if old_name not in self._config_data.instances:
1471 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1473 # Operate on a copy to not loose instance object in case of a failure
1474 inst = self._config_data.instances[old_name].Copy()
1475 inst.name = new_name
1477 for (idx, disk) in enumerate(inst.disks):
1478 if disk.dev_type == constants.LD_FILE:
1479 # rename the file paths in logical and physical id
1480 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1481 disk.logical_id = (disk.logical_id[0],
1482 utils.PathJoin(file_storage_dir, inst.name,
1484 disk.physical_id = disk.logical_id
1486 # Actually replace instance object
1487 del self._config_data.instances[old_name]
1488 self._config_data.instances[inst.name] = inst
1490 # Force update of ssconf files
1491 self._config_data.cluster.serial_no += 1
1495 @locking.ssynchronized(_config_lock)
1496 def MarkInstanceDown(self, instance_name):
1497 """Mark the status of an instance to down in the configuration.
1500 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1502 def _UnlockedGetInstanceList(self):
1503 """Get the list of instances.
1505 This function is for internal use, when the config lock is already held.
1508 return self._config_data.instances.keys()
1510 @locking.ssynchronized(_config_lock, shared=1)
1511 def GetInstanceList(self):
1512 """Get the list of instances.
1514 @return: array of instances, ex. ['instance2.example.com',
1515 'instance1.example.com']
1518 return self._UnlockedGetInstanceList()
1520 def ExpandInstanceName(self, short_name):
1521 """Attempt to expand an incomplete instance name.
1524 # Locking is done in L{ConfigWriter.GetInstanceList}
1525 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1527 def _UnlockedGetInstanceInfo(self, instance_name):
1528 """Returns information about an instance.
1530 This function is for internal use, when the config lock is already held.
1533 if instance_name not in self._config_data.instances:
1536 return self._config_data.instances[instance_name]
1538 @locking.ssynchronized(_config_lock, shared=1)
1539 def GetInstanceInfo(self, instance_name):
1540 """Returns information about an instance.
1542 It takes the information from the configuration file. Other information of
1543 an instance are taken from the live systems.
1545 @param instance_name: name of the instance, e.g.
1546 I{instance1.example.com}
1548 @rtype: L{objects.Instance}
1549 @return: the instance object
1552 return self._UnlockedGetInstanceInfo(instance_name)
1554 @locking.ssynchronized(_config_lock, shared=1)
1555 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1556 """Returns set of node group UUIDs for instance's nodes.
1561 instance = self._UnlockedGetInstanceInfo(instance_name)
1563 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1566 nodes = [instance.primary_node]
1568 nodes = instance.all_nodes
1570 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1571 for node_name in nodes)
1573 @locking.ssynchronized(_config_lock, shared=1)
1574 def GetMultiInstanceInfo(self, instances):
1575 """Get the configuration of multiple instances.
1577 @param instances: list of instance names
1579 @return: list of tuples (instance, instance_info), where
1580 instance_info is what would GetInstanceInfo return for the
1581 node, while keeping the original order
1584 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1586 @locking.ssynchronized(_config_lock, shared=1)
1587 def GetAllInstancesInfo(self):
1588 """Get the configuration of all instances.
1591 @return: dict of (instance, instance_info), where instance_info is what
1592 would GetInstanceInfo return for the node
1595 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1596 for instance in self._UnlockedGetInstanceList()])
1599 @locking.ssynchronized(_config_lock, shared=1)
1600 def GetInstancesInfoByFilter(self, filter_fn):
1601 """Get instance configuration with a filter.
1603 @type filter_fn: callable
1604 @param filter_fn: Filter function receiving instance object as parameter,
1605 returning boolean. Important: this function is called while the
1606 configuration locks is held. It must not do any complex work or call
1607 functions potentially leading to a deadlock. Ideally it doesn't call any
1608 other functions and just compares instance attributes.
1611 return dict((name, inst)
1612 for (name, inst) in self._config_data.instances.items()
1615 @locking.ssynchronized(_config_lock)
1616 def AddNode(self, node, ec_id):
1617 """Add a node to the configuration.
1619 @type node: L{objects.Node}
1620 @param node: a Node instance
1623 logging.info("Adding node %s to configuration", node.name)
1625 self._EnsureUUID(node, ec_id)
1628 node.ctime = node.mtime = time.time()
1629 self._UnlockedAddNodeToGroup(node.name, node.group)
1630 self._config_data.nodes[node.name] = node
1631 self._config_data.cluster.serial_no += 1
1634 @locking.ssynchronized(_config_lock)
1635 def RemoveNode(self, node_name):
1636 """Remove a node from the configuration.
1639 logging.info("Removing node %s from configuration", node_name)
1641 if node_name not in self._config_data.nodes:
1642 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1644 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1645 del self._config_data.nodes[node_name]
1646 self._config_data.cluster.serial_no += 1
1649 def ExpandNodeName(self, short_name):
1650 """Attempt to expand an incomplete node name.
1653 # Locking is done in L{ConfigWriter.GetNodeList}
1654 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1656 def _UnlockedGetNodeInfo(self, node_name):
1657 """Get the configuration of a node, as stored in the config.
1659 This function is for internal use, when the config lock is already
1662 @param node_name: the node name, e.g. I{node1.example.com}
1664 @rtype: L{objects.Node}
1665 @return: the node object
1668 if node_name not in self._config_data.nodes:
1671 return self._config_data.nodes[node_name]
1673 @locking.ssynchronized(_config_lock, shared=1)
1674 def GetNodeInfo(self, node_name):
1675 """Get the configuration of a node, as stored in the config.
1677 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1679 @param node_name: the node name, e.g. I{node1.example.com}
1681 @rtype: L{objects.Node}
1682 @return: the node object
1685 return self._UnlockedGetNodeInfo(node_name)
1687 @locking.ssynchronized(_config_lock, shared=1)
1688 def GetNodeInstances(self, node_name):
1689 """Get the instances of a node, as stored in the config.
1691 @param node_name: the node name, e.g. I{node1.example.com}
1693 @rtype: (list, list)
1694 @return: a tuple with two lists: the primary and the secondary instances
1699 for inst in self._config_data.instances.values():
1700 if inst.primary_node == node_name:
1701 pri.append(inst.name)
1702 if node_name in inst.secondary_nodes:
1703 sec.append(inst.name)
1706 @locking.ssynchronized(_config_lock, shared=1)
1707 def GetNodeGroupInstances(self, uuid, primary_only=False):
1708 """Get the instances of a node group.
1710 @param uuid: Node group UUID
1711 @param primary_only: Whether to only consider primary nodes
1713 @return: List of instance names in node group
1717 nodes_fn = lambda inst: [inst.primary_node]
1719 nodes_fn = lambda inst: inst.all_nodes
1721 return frozenset(inst.name
1722 for inst in self._config_data.instances.values()
1723 for node_name in nodes_fn(inst)
1724 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1726 def _UnlockedGetNodeList(self):
1727 """Return the list of nodes which are in the configuration.
1729 This function is for internal use, when the config lock is already
1735 return self._config_data.nodes.keys()
1737 @locking.ssynchronized(_config_lock, shared=1)
1738 def GetNodeList(self):
1739 """Return the list of nodes which are in the configuration.
1742 return self._UnlockedGetNodeList()
1744 def _UnlockedGetOnlineNodeList(self):
1745 """Return the list of nodes which are online.
1748 all_nodes = [self._UnlockedGetNodeInfo(node)
1749 for node in self._UnlockedGetNodeList()]
1750 return [node.name for node in all_nodes if not node.offline]
1752 @locking.ssynchronized(_config_lock, shared=1)
1753 def GetOnlineNodeList(self):
1754 """Return the list of nodes which are online.
1757 return self._UnlockedGetOnlineNodeList()
1759 @locking.ssynchronized(_config_lock, shared=1)
1760 def GetVmCapableNodeList(self):
1761 """Return the list of nodes which are not vm capable.
1764 all_nodes = [self._UnlockedGetNodeInfo(node)
1765 for node in self._UnlockedGetNodeList()]
1766 return [node.name for node in all_nodes if node.vm_capable]
1768 @locking.ssynchronized(_config_lock, shared=1)
1769 def GetNonVmCapableNodeList(self):
1770 """Return the list of nodes which are not vm capable.
1773 all_nodes = [self._UnlockedGetNodeInfo(node)
1774 for node in self._UnlockedGetNodeList()]
1775 return [node.name for node in all_nodes if not node.vm_capable]
1777 @locking.ssynchronized(_config_lock, shared=1)
1778 def GetMultiNodeInfo(self, nodes):
1779 """Get the configuration of multiple nodes.
1781 @param nodes: list of node names
1783 @return: list of tuples of (node, node_info), where node_info is
1784 what would GetNodeInfo return for the node, in the original
1788 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1790 @locking.ssynchronized(_config_lock, shared=1)
1791 def GetAllNodesInfo(self):
1792 """Get the configuration of all nodes.
1795 @return: dict of (node, node_info), where node_info is what
1796 would GetNodeInfo return for the node
1799 return self._UnlockedGetAllNodesInfo()
1801 def _UnlockedGetAllNodesInfo(self):
1802 """Gets configuration of all nodes.
1804 @note: See L{GetAllNodesInfo}
1807 return dict([(node, self._UnlockedGetNodeInfo(node))
1808 for node in self._UnlockedGetNodeList()])
1810 @locking.ssynchronized(_config_lock, shared=1)
1811 def GetNodeGroupsFromNodes(self, nodes):
1812 """Returns groups for a list of nodes.
1814 @type nodes: list of string
1815 @param nodes: List of node names
1819 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1821 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1822 """Get the number of current and maximum desired and possible candidates.
1824 @type exceptions: list
1825 @param exceptions: if passed, list of nodes that should be ignored
1827 @return: tuple of (current, desired and possible, possible)
1830 mc_now = mc_should = mc_max = 0
1831 for node in self._config_data.nodes.values():
1832 if exceptions and node.name in exceptions:
1834 if not (node.offline or node.drained) and node.master_capable:
1836 if node.master_candidate:
1838 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1839 return (mc_now, mc_should, mc_max)
1841 @locking.ssynchronized(_config_lock, shared=1)
1842 def GetMasterCandidateStats(self, exceptions=None):
1843 """Get the number of current and maximum possible candidates.
1845 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1847 @type exceptions: list
1848 @param exceptions: if passed, list of nodes that should be ignored
1850 @return: tuple of (current, max)
1853 return self._UnlockedGetMasterCandidateStats(exceptions)
1855 @locking.ssynchronized(_config_lock)
1856 def MaintainCandidatePool(self, exceptions):
1857 """Try to grow the candidate pool to the desired size.
1859 @type exceptions: list
1860 @param exceptions: if passed, list of nodes that should be ignored
1862 @return: list with the adjusted nodes (L{objects.Node} instances)
1865 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1868 node_list = self._config_data.nodes.keys()
1869 random.shuffle(node_list)
1870 for name in node_list:
1871 if mc_now >= mc_max:
1873 node = self._config_data.nodes[name]
1874 if (node.master_candidate or node.offline or node.drained or
1875 node.name in exceptions or not node.master_capable):
1877 mod_list.append(node)
1878 node.master_candidate = True
1881 if mc_now != mc_max:
1882 # this should not happen
1883 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1884 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1886 self._config_data.cluster.serial_no += 1
1891 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1892 """Add a given node to the specified group.
1895 if nodegroup_uuid not in self._config_data.nodegroups:
1896 # This can happen if a node group gets deleted between its lookup and
1897 # when we're adding the first node to it, since we don't keep a lock in
1898 # the meantime. It's ok though, as we'll fail cleanly if the node group
1899 # is not found anymore.
1900 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1901 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1902 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1904 def _UnlockedRemoveNodeFromGroup(self, node):
1905 """Remove a given node from its group.
1908 nodegroup = node.group
1909 if nodegroup not in self._config_data.nodegroups:
1910 logging.warning("Warning: node '%s' has unknown node group '%s'"
1911 " (while being removed from it)", node.name, nodegroup)
1912 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1913 if node.name not in nodegroup_obj.members:
1914 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1915 " (while being removed from it)", node.name, nodegroup)
1917 nodegroup_obj.members.remove(node.name)
1919 @locking.ssynchronized(_config_lock)
1920 def AssignGroupNodes(self, mods):
1921 """Changes the group of a number of nodes.
1923 @type mods: list of tuples; (node name, new group UUID)
1924 @param mods: Node membership modifications
1927 groups = self._config_data.nodegroups
1928 nodes = self._config_data.nodes
1932 # Try to resolve names/UUIDs first
1933 for (node_name, new_group_uuid) in mods:
1935 node = nodes[node_name]
1937 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1939 if node.group == new_group_uuid:
1940 # Node is being assigned to its current group
1941 logging.debug("Node '%s' was assigned to its current group (%s)",
1942 node_name, node.group)
1945 # Try to find current group of node
1947 old_group = groups[node.group]
1949 raise errors.ConfigurationError("Unable to find old group '%s'" %
1952 # Try to find new group for node
1954 new_group = groups[new_group_uuid]
1956 raise errors.ConfigurationError("Unable to find new group '%s'" %
1959 assert node.name in old_group.members, \
1960 ("Inconsistent configuration: node '%s' not listed in members for its"
1961 " old group '%s'" % (node.name, old_group.uuid))
1962 assert node.name not in new_group.members, \
1963 ("Inconsistent configuration: node '%s' already listed in members for"
1964 " its new group '%s'" % (node.name, new_group.uuid))
1966 resmod.append((node, old_group, new_group))
1969 for (node, old_group, new_group) in resmod:
1970 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1971 "Assigning to current group is not possible"
1973 node.group = new_group.uuid
1975 # Update members of involved groups
1976 if node.name in old_group.members:
1977 old_group.members.remove(node.name)
1978 if node.name not in new_group.members:
1979 new_group.members.append(node.name)
1981 # Update timestamps and serials (only once per node/group object)
1983 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
1987 # Force ssconf update
1988 self._config_data.cluster.serial_no += 1
1992 def _BumpSerialNo(self):
1993 """Bump up the serial number of the config.
1996 self._config_data.serial_no += 1
1997 self._config_data.mtime = time.time()
1999 def _AllUUIDObjects(self):
2000 """Returns all objects with uuid attributes.
2003 return (self._config_data.instances.values() +
2004 self._config_data.nodes.values() +
2005 self._config_data.nodegroups.values() +
2006 [self._config_data.cluster])
2008 def _OpenConfig(self, accept_foreign):
2009 """Read the config data from disk.
2012 raw_data = utils.ReadFile(self._cfg_file)
2015 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
2016 except Exception, err:
2017 raise errors.ConfigurationError(err)
2019 # Make sure the configuration has the right version
2020 _ValidateConfig(data)
2022 if (not hasattr(data, "cluster") or
2023 not hasattr(data.cluster, "rsahostkeypub")):
2024 raise errors.ConfigurationError("Incomplete configuration"
2025 " (missing cluster.rsahostkeypub)")
2027 if data.cluster.master_node != self._my_hostname and not accept_foreign:
2028 msg = ("The configuration denotes node %s as master, while my"
2029 " hostname is %s; opening a foreign configuration is only"
2030 " possible in accept_foreign mode" %
2031 (data.cluster.master_node, self._my_hostname))
2032 raise errors.ConfigurationError(msg)
2034 # Upgrade configuration if needed
2035 data.UpgradeConfig()
2037 self._config_data = data
2038 # reset the last serial as -1 so that the next write will cause
2040 self._last_cluster_serial = -1
2042 # And finally run our (custom) config upgrade sequence
2043 self._UpgradeConfig()
2045 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2047 def _UpgradeConfig(self):
2048 """Run upgrade steps that cannot be done purely in the objects.
2050 This is because some data elements need uniqueness across the
2051 whole configuration, etc.
2053 @warning: this function will call L{_WriteConfig()}, but also
2054 L{DropECReservations} so it needs to be called only from a
2055 "safe" place (the constructor). If one wanted to call it with
2056 the lock held, a DropECReservationUnlocked would need to be
2057 created first, to avoid causing deadlock.
2061 for item in self._AllUUIDObjects():
2062 if item.uuid is None:
2063 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2065 if not self._config_data.nodegroups:
2066 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2067 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2069 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2071 for node in self._config_data.nodes.values():
2073 node.group = self.LookupNodeGroup(None)
2075 # This is technically *not* an upgrade, but needs to be done both when
2076 # nodegroups are being added, and upon normally loading the config,
2077 # because the members list of a node group is discarded upon
2078 # serializing/deserializing the object.
2079 self._UnlockedAddNodeToGroup(node.name, node.group)
2082 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2083 # only called at config init time, without the lock held
2084 self.DropECReservations(_UPGRADE_CONFIG_JID)
2086 def _DistributeConfig(self, feedback_fn):
2087 """Distribute the configuration to the other nodes.
2089 Currently, this only copies the configuration file. In the future,
2090 it could be used to encapsulate the 2/3-phase update mechanism.
2100 myhostname = self._my_hostname
2101 # we can skip checking whether _UnlockedGetNodeInfo returns None
2102 # since the node list comes from _UnlocketGetNodeList, and we are
2103 # called with the lock held, so no modifications should take place
2105 for node_name in self._UnlockedGetNodeList():
2106 if node_name == myhostname:
2108 node_info = self._UnlockedGetNodeInfo(node_name)
2109 if not node_info.master_candidate:
2111 node_list.append(node_info.name)
2112 addr_list.append(node_info.primary_ip)
2114 # TODO: Use dedicated resolver talking to config writer for name resolution
2116 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2117 for to_node, to_result in result.items():
2118 msg = to_result.fail_msg
2120 msg = ("Copy of file %s to node %s failed: %s" %
2121 (self._cfg_file, to_node, msg))
2131 def _WriteConfig(self, destination=None, feedback_fn=None):
2132 """Write the configuration data to persistent storage.
2135 assert feedback_fn is None or callable(feedback_fn)
2137 # Warn on config errors, but don't abort the save - the
2138 # configuration has already been modified, and we can't revert;
2139 # the best we can do is to warn the user and save as is, leaving
2140 # recovery to the user
2141 config_errors = self._UnlockedVerifyConfig()
2143 errmsg = ("Configuration data is not consistent: %s" %
2144 (utils.CommaJoin(config_errors)))
2145 logging.critical(errmsg)
2149 if destination is None:
2150 destination = self._cfg_file
2151 self._BumpSerialNo()
2152 txt = serializer.Dump(self._config_data.ToDict())
2154 getents = self._getents()
2156 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2157 close=False, gid=getents.confd_gid, mode=0640)
2158 except errors.LockError:
2159 raise errors.ConfigurationError("The configuration file has been"
2160 " modified since the last write, cannot"
2163 self._cfg_id = utils.GetFileID(fd=fd)
2167 self.write_count += 1
2169 # and redistribute the config file to master candidates
2170 self._DistributeConfig(feedback_fn)
2172 # Write ssconf files on all nodes (including locally)
2173 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2174 if not self._offline:
2175 result = self._GetRpc(None).call_write_ssconf_files(
2176 self._UnlockedGetOnlineNodeList(),
2177 self._UnlockedGetSsconfValues())
2179 for nname, nresu in result.items():
2180 msg = nresu.fail_msg
2182 errmsg = ("Error while uploading ssconf files to"
2183 " node %s: %s" % (nname, msg))
2184 logging.warning(errmsg)
2189 self._last_cluster_serial = self._config_data.cluster.serial_no
2191 def _UnlockedGetSsconfValues(self):
2192 """Return the values needed by ssconf.
2195 @return: a dictionary with keys the ssconf names and values their
2200 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2201 node_names = utils.NiceSort(self._UnlockedGetNodeList())
2202 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2203 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2204 for ninfo in node_info]
2205 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2206 for ninfo in node_info]
2208 instance_data = fn(instance_names)
2209 off_data = fn(node.name for node in node_info if node.offline)
2210 on_data = fn(node.name for node in node_info if not node.offline)
2211 mc_data = fn(node.name for node in node_info if node.master_candidate)
2212 mc_ips_data = fn(node.primary_ip for node in node_info
2213 if node.master_candidate)
2214 node_data = fn(node_names)
2215 node_pri_ips_data = fn(node_pri_ips)
2216 node_snd_ips_data = fn(node_snd_ips)
2218 cluster = self._config_data.cluster
2219 cluster_tags = fn(cluster.GetTags())
2221 hypervisor_list = fn(cluster.enabled_hypervisors)
2223 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2225 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2226 self._config_data.nodegroups.values()]
2227 nodegroups_data = fn(utils.NiceSort(nodegroups))
2228 networks = ["%s %s" % (net.uuid, net.name) for net in
2229 self._config_data.networks.values()]
2230 networks_data = fn(utils.NiceSort(networks))
2233 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2234 constants.SS_CLUSTER_TAGS: cluster_tags,
2235 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2236 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2237 constants.SS_MASTER_CANDIDATES: mc_data,
2238 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2239 constants.SS_MASTER_IP: cluster.master_ip,
2240 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2241 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2242 constants.SS_MASTER_NODE: cluster.master_node,
2243 constants.SS_NODE_LIST: node_data,
2244 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2245 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2246 constants.SS_OFFLINE_NODES: off_data,
2247 constants.SS_ONLINE_NODES: on_data,
2248 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2249 constants.SS_INSTANCE_LIST: instance_data,
2250 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2251 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2252 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2253 constants.SS_UID_POOL: uid_pool,
2254 constants.SS_NODEGROUPS: nodegroups_data,
2255 constants.SS_NETWORKS: networks_data,
2257 bad_values = [(k, v) for k, v in ssconf_values.items()
2258 if not isinstance(v, (str, basestring))]
2260 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2261 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2262 " values: %s" % err)
2263 return ssconf_values
2265 @locking.ssynchronized(_config_lock, shared=1)
2266 def GetSsconfValues(self):
2267 """Wrapper using lock around _UnlockedGetSsconf().
2270 return self._UnlockedGetSsconfValues()
2272 @locking.ssynchronized(_config_lock, shared=1)
2273 def GetVGName(self):
2274 """Return the volume group name.
2277 return self._config_data.cluster.volume_group_name
2279 @locking.ssynchronized(_config_lock)
2280 def SetVGName(self, vg_name):
2281 """Set the volume group name.
2284 self._config_data.cluster.volume_group_name = vg_name
2285 self._config_data.cluster.serial_no += 1
2288 @locking.ssynchronized(_config_lock, shared=1)
2289 def GetDRBDHelper(self):
2290 """Return DRBD usermode helper.
2293 return self._config_data.cluster.drbd_usermode_helper
2295 @locking.ssynchronized(_config_lock)
2296 def SetDRBDHelper(self, drbd_helper):
2297 """Set DRBD usermode helper.
2300 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2301 self._config_data.cluster.serial_no += 1
2304 @locking.ssynchronized(_config_lock, shared=1)
2305 def GetMACPrefix(self):
2306 """Return the mac prefix.
2309 return self._config_data.cluster.mac_prefix
2311 @locking.ssynchronized(_config_lock, shared=1)
2312 def GetClusterInfo(self):
2313 """Returns information about the cluster
2315 @rtype: L{objects.Cluster}
2316 @return: the cluster object
2319 return self._config_data.cluster
2321 @locking.ssynchronized(_config_lock, shared=1)
2322 def HasAnyDiskOfType(self, dev_type):
2323 """Check if in there is at disk of the given type in the configuration.
2326 return self._config_data.HasAnyDiskOfType(dev_type)
2328 @locking.ssynchronized(_config_lock)
2329 def Update(self, target, feedback_fn, ec_id=None):
2330 """Notify function to be called after updates.
2332 This function must be called when an object (as returned by
2333 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2334 caller wants the modifications saved to the backing store. Note
2335 that all modified objects will be saved, but the target argument
2336 is the one the caller wants to ensure that it's saved.
2338 @param target: an instance of either L{objects.Cluster},
2339 L{objects.Node} or L{objects.Instance} which is existing in
2341 @param feedback_fn: Callable feedback function
2344 if self._config_data is None:
2345 raise errors.ProgrammerError("Configuration file not read,"
2347 update_serial = False
2348 if isinstance(target, objects.Cluster):
2349 test = target == self._config_data.cluster
2350 elif isinstance(target, objects.Node):
2351 test = target in self._config_data.nodes.values()
2352 update_serial = True
2353 elif isinstance(target, objects.Instance):
2354 test = target in self._config_data.instances.values()
2355 elif isinstance(target, objects.NodeGroup):
2356 test = target in self._config_data.nodegroups.values()
2357 elif isinstance(target, objects.Network):
2358 test = target in self._config_data.networks.values()
2360 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2361 " ConfigWriter.Update" % type(target))
2363 raise errors.ConfigurationError("Configuration updated since object"
2364 " has been read or unknown object")
2365 target.serial_no += 1
2366 target.mtime = now = time.time()
2369 # for node updates, we need to increase the cluster serial too
2370 self._config_data.cluster.serial_no += 1
2371 self._config_data.cluster.mtime = now
2373 if isinstance(target, objects.Instance):
2374 self._UnlockedReleaseDRBDMinors(target.name)
2376 if ec_id is not None:
2377 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
2378 self._UnlockedCommitTemporaryIps(ec_id)
2380 self._WriteConfig(feedback_fn=feedback_fn)
2382 @locking.ssynchronized(_config_lock)
2383 def DropECReservations(self, ec_id):
2384 """Drop per-execution-context reservations
2387 for rm in self._all_rms:
2388 rm.DropECReservations(ec_id)
2390 @locking.ssynchronized(_config_lock, shared=1)
2391 def GetAllNetworksInfo(self):
2392 """Get the configuration of all networks
2395 return dict(self._config_data.networks)
2397 def _UnlockedGetNetworkList(self):
2398 """Get the list of networks.
2400 This function is for internal use, when the config lock is already held.
2403 return self._config_data.networks.keys()
2405 @locking.ssynchronized(_config_lock, shared=1)
2406 def GetNetworkList(self):
2407 """Get the list of networks.
2409 @return: array of networks, ex. ["main", "vlan100", "200]
2412 return self._UnlockedGetNetworkList()
2414 @locking.ssynchronized(_config_lock, shared=1)
2415 def GetNetworkNames(self):
2416 """Get a list of network names
2420 for net in self._config_data.networks.values()]
2423 def _UnlockedGetNetwork(self, uuid):
2424 """Returns information about a network.
2426 This function is for internal use, when the config lock is already held.
2429 if uuid not in self._config_data.networks:
2432 return self._config_data.networks[uuid]
2434 @locking.ssynchronized(_config_lock, shared=1)
2435 def GetNetwork(self, uuid):
2436 """Returns information about a network.
2438 It takes the information from the configuration file.
2440 @param uuid: UUID of the network
2442 @rtype: L{objects.Network}
2443 @return: the network object
2446 return self._UnlockedGetNetwork(uuid)
2448 @locking.ssynchronized(_config_lock)
2449 def AddNetwork(self, net, ec_id, check_uuid=True):
2450 """Add a network to the configuration.
2452 @type net: L{objects.Network}
2453 @param net: the Network object to add
2455 @param ec_id: unique id for the job to use when creating a missing UUID
2458 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2461 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2462 """Add a network to the configuration.
2465 logging.info("Adding network %s to configuration", net.name)
2468 self._EnsureUUID(net, ec_id)
2470 existing_uuid = self._UnlockedLookupNetwork(net.name)
2472 raise errors.OpPrereqError("Desired network name '%s' already"
2473 " exists as a network (UUID: %s)" %
2474 (net.name, existing_uuid),
2475 errors.ECODE_EXISTS)
2477 self._config_data.networks[net.uuid] = net
2478 self._config_data.cluster.serial_no += 1
2480 def _UnlockedLookupNetwork(self, target):
2481 """Lookup a network's UUID.
2483 @type target: string
2484 @param target: network name or UUID
2486 @return: network UUID
2487 @raises errors.OpPrereqError: when the target network cannot be found
2490 if target in self._config_data.networks:
2492 for net in self._config_data.networks.values():
2493 if net.name == target:
2497 @locking.ssynchronized(_config_lock, shared=1)
2498 def LookupNetwork(self, target):
2499 """Lookup a network's UUID.
2501 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2503 @type target: string
2504 @param target: network name or UUID
2506 @return: network UUID
2509 return self._UnlockedLookupNetwork(target)
2511 @locking.ssynchronized(_config_lock)
2512 def RemoveNetwork(self, network_uuid):
2513 """Remove a network from the configuration.
2515 @type network_uuid: string
2516 @param network_uuid: the UUID of the network to remove
2519 logging.info("Removing network %s from configuration", network_uuid)
2521 if network_uuid not in self._config_data.networks:
2522 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2524 del self._config_data.networks[network_uuid]
2525 self._config_data.cluster.serial_no += 1
2528 def _UnlockedGetGroupNetParams(self, net, node):
2529 """Get the netparams (mode, link) of a network.
2531 Get a network's netparams for a given node.
2534 @param net: network name
2536 @param node: node name
2537 @rtype: dict or None
2541 net_uuid = self._UnlockedLookupNetwork(net)
2542 if net_uuid is None:
2545 node_info = self._UnlockedGetNodeInfo(node)
2546 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2547 netparams = nodegroup_info.networks.get(net_uuid, None)
2551 @locking.ssynchronized(_config_lock, shared=1)
2552 def GetGroupNetParams(self, net, node):
2553 """Locking wrapper of _UnlockedGetGroupNetParams()
2556 return self._UnlockedGetGroupNetParams(net, node)
2558 @locking.ssynchronized(_config_lock, shared=1)
2559 def CheckIPInNodeGroup(self, ip, node):
2560 """Check for conflictig IP.
2563 @param ip: ip address
2565 @param node: node name
2566 @rtype: (string, dict) or (None, None)
2567 @return: (network name, netparams)
2572 node_info = self._UnlockedGetNodeInfo(node)
2573 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2574 for net_uuid in nodegroup_info.networks.keys():
2575 net_info = self._UnlockedGetNetwork(net_uuid)
2576 pool = network.AddressPool(net_info)
2577 if pool.Contains(ip):
2578 return (net_info.name, nodegroup_info.networks[net_uuid])