4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Configuration management for Ganeti
24 This module provides the interface to the Ganeti cluster configuration.
26 The configuration data is stored on every node but is updated on the master
27 only. After each update, the master distributes the data to the other nodes.
29 Currently, the data storage format is JSON. YAML was slow and consuming too
34 # pylint: disable=R0904
35 # R0904: Too many public methods
43 from ganeti import errors
44 from ganeti import locking
45 from ganeti import utils
46 from ganeti import constants
47 from ganeti import rpc
48 from ganeti import objects
49 from ganeti import serializer
50 from ganeti import uidpool
51 from ganeti import netutils
52 from ganeti import runtime
53 from ganeti import pathutils
54 from ganeti import network
57 _config_lock = locking.SharedLock("ConfigWriter")
59 # job id used for resource management at config upgrade time
60 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
63 def _ValidateConfig(data):
64 """Verifies that a configuration objects looks valid.
66 This only verifies the version of the configuration.
68 @raise errors.ConfigurationError: if the version differs from what
72 if data.version != constants.CONFIG_VERSION:
73 raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
76 class TemporaryReservationManager:
77 """A temporary resource reservation manager.
79 This is used to reserve resources in a job, before using them, making sure
80 other jobs cannot get them in the meantime.
84 self._ec_reserved = {}
86 def Reserved(self, resource):
87 for holder_reserved in self._ec_reserved.values():
88 if resource in holder_reserved:
92 def Reserve(self, ec_id, resource):
93 if self.Reserved(resource):
94 raise errors.ReservationError("Duplicate reservation for resource '%s'"
96 if ec_id not in self._ec_reserved:
97 self._ec_reserved[ec_id] = set([resource])
99 self._ec_reserved[ec_id].add(resource)
101 def DropECReservations(self, ec_id):
102 if ec_id in self._ec_reserved:
103 del self._ec_reserved[ec_id]
105 def GetReserved(self):
107 for holder_reserved in self._ec_reserved.values():
108 all_reserved.update(holder_reserved)
111 def GetECReserved(self, ec_id):
113 if ec_id in self._ec_reserved:
114 ec_reserved.update(self._ec_reserved[ec_id])
118 def Generate(self, existing, generate_one_fn, ec_id):
119 """Generate a new resource of this type
122 assert callable(generate_one_fn)
124 all_elems = self.GetReserved()
125 all_elems.update(existing)
128 new_resource = generate_one_fn()
129 if new_resource is not None and new_resource not in all_elems:
132 raise errors.ConfigurationError("Not able generate new resource"
133 " (last tried: %s)" % new_resource)
134 self.Reserve(ec_id, new_resource)
138 def _MatchNameComponentIgnoreCase(short_name, names):
139 """Wrapper around L{utils.text.MatchNameComponent}.
142 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
145 def _CheckInstanceDiskIvNames(disks):
146 """Checks if instance's disks' C{iv_name} attributes are in order.
148 @type disks: list of L{objects.Disk}
149 @param disks: List of disks
150 @rtype: list of tuples; (int, string, string)
151 @return: List of wrongly named disks, each tuple contains disk index,
152 expected and actual name
157 for (idx, disk) in enumerate(disks):
158 exp_iv_name = "disk/%s" % idx
159 if disk.iv_name != exp_iv_name:
160 result.append((idx, exp_iv_name, disk.iv_name))
166 """The interface to the cluster configuration.
168 @ivar _temporary_lvs: reservation manager for temporary LVs
169 @ivar _all_rms: a list of all temporary reservation managers
172 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
173 accept_foreign=False):
175 self._lock = _config_lock
176 self._config_data = None
177 self._offline = offline
179 self._cfg_file = pathutils.CLUSTER_CONF_FILE
181 self._cfg_file = cfg_file
182 self._getents = _getents
183 self._temporary_ids = TemporaryReservationManager()
184 self._temporary_drbds = {}
185 self._temporary_macs = TemporaryReservationManager()
186 self._temporary_secrets = TemporaryReservationManager()
187 self._temporary_lvs = TemporaryReservationManager()
188 self._temporary_ips = TemporaryReservationManager()
189 self._all_rms = [self._temporary_ids, self._temporary_macs,
190 self._temporary_secrets, self._temporary_lvs,
192 # Note: in order to prevent errors when resolving our name in
193 # _DistributeConfig, we compute it here once and reuse it; it's
194 # better to raise an error before starting to modify the config
195 # file than after it was modified
196 self._my_hostname = netutils.Hostname.GetSysName()
197 self._last_cluster_serial = -1
200 self._OpenConfig(accept_foreign)
202 def _GetRpc(self, address_list):
203 """Returns RPC runner for configuration.
206 return rpc.ConfigRunner(self._context, address_list)
208 def SetContext(self, context):
209 """Sets Ganeti context.
212 self._context = context
214 # this method needs to be static, so that we can call it on the class
217 """Check if the cluster is configured.
220 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
222 def _GenerateOneMAC(self):
223 """Generate one mac address
226 prefix = self._config_data.cluster.mac_prefix
227 byte1 = random.randrange(0, 256)
228 byte2 = random.randrange(0, 256)
229 byte3 = random.randrange(0, 256)
230 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
233 @locking.ssynchronized(_config_lock, shared=1)
234 def GetNdParams(self, node):
235 """Get the node params populated with cluster defaults.
237 @type node: L{objects.Node}
238 @param node: The node we want to know the params for
239 @return: A dict with the filled in node params
242 nodegroup = self._UnlockedGetNodeGroup(node.group)
243 return self._config_data.cluster.FillND(node, nodegroup)
245 @locking.ssynchronized(_config_lock, shared=1)
246 def GetInstanceDiskParams(self, instance):
247 """Get the disk params populated with inherit chain.
249 @type instance: L{objects.Instance}
250 @param instance: The instance we want to know the params for
251 @return: A dict with the filled in disk params
254 node = self._UnlockedGetNodeInfo(instance.primary_node)
255 nodegroup = self._UnlockedGetNodeGroup(node.group)
256 return self._UnlockedGetGroupDiskParams(nodegroup)
258 @locking.ssynchronized(_config_lock, shared=1)
259 def GetGroupDiskParams(self, group):
260 """Get the disk params populated with inherit chain.
262 @type group: L{objects.NodeGroup}
263 @param group: The group we want to know the params for
264 @return: A dict with the filled in disk params
267 return self._UnlockedGetGroupDiskParams(group)
269 def _UnlockedGetGroupDiskParams(self, group):
270 """Get the disk params populated with inherit chain down to node-group.
272 @type group: L{objects.NodeGroup}
273 @param group: The group we want to know the params for
274 @return: A dict with the filled in disk params
277 return self._config_data.cluster.SimpleFillDP(group.diskparams)
279 @locking.ssynchronized(_config_lock, shared=1)
280 def GenerateMAC(self, ec_id):
281 """Generate a MAC for an instance.
283 This should check the current instances for duplicates.
286 existing = self._AllMACs()
287 return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
289 @locking.ssynchronized(_config_lock, shared=1)
290 def ReserveMAC(self, mac, ec_id):
291 """Reserve a MAC for an instance.
293 This only checks instances managed by this cluster, it does not
294 check for potential collisions elsewhere.
297 all_macs = self._AllMACs()
299 raise errors.ReservationError("mac already in use")
301 self._temporary_macs.Reserve(ec_id, mac)
303 def _UnlockedCommitTemporaryIps(self, ec_id):
304 """Commit all reserved IP address to their respective pools
307 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
308 self._UnlockedCommitIp(action, net_uuid, address)
310 def _UnlockedCommitIp(self, action, net_uuid, address):
311 """Commit a reserved IP address to an IP pool.
313 The IP address is taken from the network's IP pool and marked as reserved.
316 nobj = self._UnlockedGetNetwork(net_uuid)
317 pool = network.AddressPool(nobj)
318 if action == 'reserve':
319 pool.Reserve(address)
320 elif action == 'release':
321 pool.Release(address)
323 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
324 """Give a specific IP address back to an IP pool.
326 The IP address is returned to the IP pool designated by pool_id and marked
330 nobj = self._UnlockedGetNetwork(net_uuid)
331 pool = network.AddressPool(nobj)
332 self._temporary_ips.Reserve(ec_id, ('release', address, net_uuid))
334 @locking.ssynchronized(_config_lock, shared=1)
335 def ReleaseIp(self, network, address, ec_id):
336 """Give a specified IP address back to an IP pool.
338 This is just a wrapper around _UnlockedReleaseIp.
341 net_uuid = self._UnlockedLookupNetwork(network)
343 self._UnlockedReleaseIp(net_uuid, address, ec_id)
345 @locking.ssynchronized(_config_lock, shared=1)
346 def GenerateIp(self, net, ec_id):
347 """Find a free IPv4 address for an instance.
350 net_uuid = self._UnlockedLookupNetwork(net)
351 nobj = self._UnlockedGetNetwork(net_uuid)
352 pool = network.AddressPool(nobj)
353 gen_free = pool.GenerateFree()
358 except StopIteration:
359 raise errors.ReservationError("Cannot generate IP. Network is full")
360 return ("reserve", ip, net_uuid)
362 _ ,address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
365 def _UnlockedReserveIp(self, net_uuid, address, ec_id):
366 """Reserve a given IPv4 address for use by an instance.
369 nobj = self._UnlockedGetNetwork(net_uuid)
370 pool = network.AddressPool(nobj)
372 isreserved = pool.IsReserved(address)
373 except errors.AddressPoolError:
374 raise errors.ReservationError("IP address not in network")
376 raise errors.ReservationError("IP address already in use")
378 return self._temporary_ips.Reserve(ec_id, ('reserve', address, net_uuid))
381 @locking.ssynchronized(_config_lock, shared=1)
382 def ReserveIp(self, net, address, ec_id):
383 """Reserve a given IPv4 address for use by an instance.
386 net_uuid = self._UnlockedLookupNetwork(net)
388 return self._UnlockedReserveIp(net_uuid, address, ec_id)
390 @locking.ssynchronized(_config_lock, shared=1)
391 def ReserveLV(self, lv_name, ec_id):
392 """Reserve an VG/LV pair for an instance.
394 @type lv_name: string
395 @param lv_name: the logical volume name to reserve
398 all_lvs = self._AllLVs()
399 if lv_name in all_lvs:
400 raise errors.ReservationError("LV already in use")
402 self._temporary_lvs.Reserve(ec_id, lv_name)
404 @locking.ssynchronized(_config_lock, shared=1)
405 def GenerateDRBDSecret(self, ec_id):
406 """Generate a DRBD secret.
408 This checks the current disks for duplicates.
411 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
412 utils.GenerateSecret,
416 """Compute the list of all LVs.
420 for instance in self._config_data.instances.values():
421 node_data = instance.MapLVsByNode()
422 for lv_list in node_data.values():
423 lvnames.update(lv_list)
426 def _AllIDs(self, include_temporary):
427 """Compute the list of all UUIDs and names we have.
429 @type include_temporary: boolean
430 @param include_temporary: whether to include the _temporary_ids set
432 @return: a set of IDs
436 if include_temporary:
437 existing.update(self._temporary_ids.GetReserved())
438 existing.update(self._AllLVs())
439 existing.update(self._config_data.instances.keys())
440 existing.update(self._config_data.nodes.keys())
441 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
444 def _GenerateUniqueID(self, ec_id):
445 """Generate an unique UUID.
447 This checks the current node, instances and disk names for
451 @return: the unique id
454 existing = self._AllIDs(include_temporary=False)
455 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
457 @locking.ssynchronized(_config_lock, shared=1)
458 def GenerateUniqueID(self, ec_id):
459 """Generate an unique ID.
461 This is just a wrapper over the unlocked version.
464 @param ec_id: unique id for the job to reserve the id to
467 return self._GenerateUniqueID(ec_id)
470 """Return all MACs present in the config.
473 @return: the list of all MACs
477 for instance in self._config_data.instances.values():
478 for nic in instance.nics:
479 result.append(nic.mac)
483 def _AllDRBDSecrets(self):
484 """Return all DRBD secrets present in the config.
487 @return: the list of all DRBD secrets
490 def helper(disk, result):
491 """Recursively gather secrets from this disk."""
492 if disk.dev_type == constants.DT_DRBD8:
493 result.append(disk.logical_id[5])
495 for child in disk.children:
496 helper(child, result)
499 for instance in self._config_data.instances.values():
500 for disk in instance.disks:
505 def _CheckDiskIDs(self, disk, l_ids, p_ids):
506 """Compute duplicate disk IDs
508 @type disk: L{objects.Disk}
509 @param disk: the disk at which to start searching
511 @param l_ids: list of current logical ids
513 @param p_ids: list of current physical ids
515 @return: a list of error messages
519 if disk.logical_id is not None:
520 if disk.logical_id in l_ids:
521 result.append("duplicate logical id %s" % str(disk.logical_id))
523 l_ids.append(disk.logical_id)
524 if disk.physical_id is not None:
525 if disk.physical_id in p_ids:
526 result.append("duplicate physical id %s" % str(disk.physical_id))
528 p_ids.append(disk.physical_id)
531 for child in disk.children:
532 result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
535 def _UnlockedVerifyConfig(self):
539 @return: a list of error messages; a non-empty list signifies
543 # pylint: disable=R0914
547 data = self._config_data
548 cluster = data.cluster
552 # global cluster checks
553 if not cluster.enabled_hypervisors:
554 result.append("enabled hypervisors list doesn't have any entries")
555 invalid_hvs = set(cluster.enabled_hypervisors) - constants.HYPER_TYPES
557 result.append("enabled hypervisors contains invalid entries: %s" %
559 missing_hvp = (set(cluster.enabled_hypervisors) -
560 set(cluster.hvparams.keys()))
562 result.append("hypervisor parameters missing for the enabled"
563 " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
565 if cluster.master_node not in data.nodes:
566 result.append("cluster has invalid primary node '%s'" %
569 def _helper(owner, attr, value, template):
571 utils.ForceDictType(value, template)
572 except errors.GenericError, err:
573 result.append("%s has invalid %s: %s" % (owner, attr, err))
575 def _helper_nic(owner, params):
577 objects.NIC.CheckParameterSyntax(params)
578 except errors.ConfigurationError, err:
579 result.append("%s has invalid nicparams: %s" % (owner, err))
581 def _helper_ipolicy(owner, params, check_std):
583 objects.InstancePolicy.CheckParameterSyntax(params, check_std)
584 except errors.ConfigurationError, err:
585 result.append("%s has invalid instance policy: %s" % (owner, err))
587 def _helper_ispecs(owner, params):
588 for key, value in params.items():
589 if key in constants.IPOLICY_ISPECS:
590 fullkey = "ipolicy/" + key
591 _helper(owner, fullkey, value, constants.ISPECS_PARAMETER_TYPES)
593 # FIXME: assuming list type
594 if key in constants.IPOLICY_PARAMETERS:
598 if not isinstance(value, exp_type):
599 result.append("%s has invalid instance policy: for %s,"
600 " expecting %s, got %s" %
601 (owner, key, exp_type.__name__, type(value)))
603 # check cluster parameters
604 _helper("cluster", "beparams", cluster.SimpleFillBE({}),
605 constants.BES_PARAMETER_TYPES)
606 _helper("cluster", "nicparams", cluster.SimpleFillNIC({}),
607 constants.NICS_PARAMETER_TYPES)
608 _helper_nic("cluster", cluster.SimpleFillNIC({}))
609 _helper("cluster", "ndparams", cluster.SimpleFillND({}),
610 constants.NDS_PARAMETER_TYPES)
611 _helper_ipolicy("cluster", cluster.SimpleFillIPolicy({}), True)
612 _helper_ispecs("cluster", cluster.SimpleFillIPolicy({}))
614 # per-instance checks
615 for instance_name in data.instances:
616 instance = data.instances[instance_name]
617 if instance.name != instance_name:
618 result.append("instance '%s' is indexed by wrong name '%s'" %
619 (instance.name, instance_name))
620 if instance.primary_node not in data.nodes:
621 result.append("instance '%s' has invalid primary node '%s'" %
622 (instance_name, instance.primary_node))
623 for snode in instance.secondary_nodes:
624 if snode not in data.nodes:
625 result.append("instance '%s' has invalid secondary node '%s'" %
626 (instance_name, snode))
627 for idx, nic in enumerate(instance.nics):
628 if nic.mac in seen_macs:
629 result.append("instance '%s' has NIC %d mac %s duplicate" %
630 (instance_name, idx, nic.mac))
632 seen_macs.append(nic.mac)
634 filled = cluster.SimpleFillNIC(nic.nicparams)
635 owner = "instance %s nic %d" % (instance.name, idx)
636 _helper(owner, "nicparams",
637 filled, constants.NICS_PARAMETER_TYPES)
638 _helper_nic(owner, filled)
641 if instance.beparams:
642 _helper("instance %s" % instance.name, "beparams",
643 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES)
645 # gather the drbd ports for duplicate checks
646 for (idx, dsk) in enumerate(instance.disks):
647 if dsk.dev_type in constants.LDS_DRBD:
648 tcp_port = dsk.logical_id[2]
649 if tcp_port not in ports:
651 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
652 # gather network port reservation
653 net_port = getattr(instance, "network_port", None)
654 if net_port is not None:
655 if net_port not in ports:
657 ports[net_port].append((instance.name, "network port"))
659 # instance disk verify
660 for idx, disk in enumerate(instance.disks):
661 result.extend(["instance '%s' disk %d error: %s" %
662 (instance.name, idx, msg) for msg in disk.Verify()])
663 result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
665 wrong_names = _CheckInstanceDiskIvNames(instance.disks)
667 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
668 (idx, exp_name, actual_name))
669 for (idx, exp_name, actual_name) in wrong_names)
671 result.append("Instance '%s' has wrongly named disks: %s" %
672 (instance.name, tmp))
674 # cluster-wide pool of free ports
675 for free_port in cluster.tcpudp_port_pool:
676 if free_port not in ports:
677 ports[free_port] = []
678 ports[free_port].append(("cluster", "port marked as free"))
680 # compute tcp/udp duplicate ports
686 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
687 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
689 # highest used tcp port check
691 if keys[-1] > cluster.highest_used_port:
692 result.append("Highest used port mismatch, saved %s, computed %s" %
693 (cluster.highest_used_port, keys[-1]))
695 if not data.nodes[cluster.master_node].master_candidate:
696 result.append("Master node is not a master candidate")
698 # master candidate checks
699 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
701 result.append("Not enough master candidates: actual %d, target %d" %
705 for node_name, node in data.nodes.items():
706 if node.name != node_name:
707 result.append("Node '%s' is indexed by wrong name '%s'" %
708 (node.name, node_name))
709 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
710 result.append("Node %s state is invalid: master_candidate=%s,"
711 " drain=%s, offline=%s" %
712 (node.name, node.master_candidate, node.drained,
714 if node.group not in data.nodegroups:
715 result.append("Node '%s' has invalid group '%s'" %
716 (node.name, node.group))
718 _helper("node %s" % node.name, "ndparams",
719 cluster.FillND(node, data.nodegroups[node.group]),
720 constants.NDS_PARAMETER_TYPES)
723 nodegroups_names = set()
724 for nodegroup_uuid in data.nodegroups:
725 nodegroup = data.nodegroups[nodegroup_uuid]
726 if nodegroup.uuid != nodegroup_uuid:
727 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
728 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
729 if utils.UUID_RE.match(nodegroup.name.lower()):
730 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
731 (nodegroup.name, nodegroup.uuid))
732 if nodegroup.name in nodegroups_names:
733 result.append("duplicate node group name '%s'" % nodegroup.name)
735 nodegroups_names.add(nodegroup.name)
736 group_name = "group %s" % nodegroup.name
737 _helper_ipolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
739 _helper_ispecs(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy))
740 if nodegroup.ndparams:
741 _helper(group_name, "ndparams",
742 cluster.SimpleFillND(nodegroup.ndparams),
743 constants.NDS_PARAMETER_TYPES)
746 _, duplicates = self._UnlockedComputeDRBDMap()
747 for node, minor, instance_a, instance_b in duplicates:
748 result.append("DRBD minor %d on node %s is assigned twice to instances"
749 " %s and %s" % (minor, node, instance_a, instance_b))
752 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
755 def _AddIpAddress(ip, name):
756 ips.setdefault(ip, []).append(name)
758 _AddIpAddress(cluster.master_ip, "cluster_ip")
760 for node in data.nodes.values():
761 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
762 if node.secondary_ip != node.primary_ip:
763 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
765 for instance in data.instances.values():
766 for idx, nic in enumerate(instance.nics):
770 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
771 nic_mode = nicparams[constants.NIC_MODE]
772 nic_link = nicparams[constants.NIC_LINK]
774 if nic_mode == constants.NIC_MODE_BRIDGED:
775 link = "bridge:%s" % nic_link
776 elif nic_mode == constants.NIC_MODE_ROUTED:
777 link = "route:%s" % nic_link
779 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
781 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
782 "instance:%s/nic:%d" % (instance.name, idx))
784 for ip, owners in ips.items():
786 result.append("IP address %s is used by multiple owners: %s" %
787 (ip, utils.CommaJoin(owners)))
791 @locking.ssynchronized(_config_lock, shared=1)
792 def VerifyConfig(self):
795 This is just a wrapper over L{_UnlockedVerifyConfig}.
798 @return: a list of error messages; a non-empty list signifies
802 return self._UnlockedVerifyConfig()
804 def _UnlockedSetDiskID(self, disk, node_name):
805 """Convert the unique ID to the ID needed on the target nodes.
807 This is used only for drbd, which needs ip/port configuration.
809 The routine descends down and updates its children also, because
810 this helps when the only the top device is passed to the remote
813 This function is for internal use, when the config lock is already held.
817 for child in disk.children:
818 self._UnlockedSetDiskID(child, node_name)
820 if disk.logical_id is None and disk.physical_id is not None:
822 if disk.dev_type == constants.LD_DRBD8:
823 pnode, snode, port, pminor, sminor, secret = disk.logical_id
824 if node_name not in (pnode, snode):
825 raise errors.ConfigurationError("DRBD device not knowing node %s" %
827 pnode_info = self._UnlockedGetNodeInfo(pnode)
828 snode_info = self._UnlockedGetNodeInfo(snode)
829 if pnode_info is None or snode_info is None:
830 raise errors.ConfigurationError("Can't find primary or secondary node"
831 " for %s" % str(disk))
832 p_data = (pnode_info.secondary_ip, port)
833 s_data = (snode_info.secondary_ip, port)
834 if pnode == node_name:
835 disk.physical_id = p_data + s_data + (pminor, secret)
836 else: # it must be secondary, we tested above
837 disk.physical_id = s_data + p_data + (sminor, secret)
839 disk.physical_id = disk.logical_id
842 @locking.ssynchronized(_config_lock)
843 def SetDiskID(self, disk, node_name):
844 """Convert the unique ID to the ID needed on the target nodes.
846 This is used only for drbd, which needs ip/port configuration.
848 The routine descends down and updates its children also, because
849 this helps when the only the top device is passed to the remote
853 return self._UnlockedSetDiskID(disk, node_name)
855 @locking.ssynchronized(_config_lock)
856 def AddTcpUdpPort(self, port):
857 """Adds a new port to the available port pool.
859 @warning: this method does not "flush" the configuration (via
860 L{_WriteConfig}); callers should do that themselves once the
861 configuration is stable
864 if not isinstance(port, int):
865 raise errors.ProgrammerError("Invalid type passed for port")
867 self._config_data.cluster.tcpudp_port_pool.add(port)
869 @locking.ssynchronized(_config_lock, shared=1)
870 def GetPortList(self):
871 """Returns a copy of the current port list.
874 return self._config_data.cluster.tcpudp_port_pool.copy()
876 @locking.ssynchronized(_config_lock)
877 def AllocatePort(self):
880 The port will be taken from the available port pool or from the
881 default port range (and in this case we increase
885 # If there are TCP/IP ports configured, we use them first.
886 if self._config_data.cluster.tcpudp_port_pool:
887 port = self._config_data.cluster.tcpudp_port_pool.pop()
889 port = self._config_data.cluster.highest_used_port + 1
890 if port >= constants.LAST_DRBD_PORT:
891 raise errors.ConfigurationError("The highest used port is greater"
892 " than %s. Aborting." %
893 constants.LAST_DRBD_PORT)
894 self._config_data.cluster.highest_used_port = port
899 def _UnlockedComputeDRBDMap(self):
900 """Compute the used DRBD minor/nodes.
903 @return: dictionary of node_name: dict of minor: instance_name;
904 the returned dict will have all the nodes in it (even if with
905 an empty list), and a list of duplicates; if the duplicates
906 list is not empty, the configuration is corrupted and its caller
907 should raise an exception
910 def _AppendUsedPorts(instance_name, disk, used):
912 if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
913 node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
914 for node, port in ((node_a, minor_a), (node_b, minor_b)):
915 assert node in used, ("Node '%s' of instance '%s' not found"
916 " in node list" % (node, instance_name))
917 if port in used[node]:
918 duplicates.append((node, port, instance_name, used[node][port]))
920 used[node][port] = instance_name
922 for child in disk.children:
923 duplicates.extend(_AppendUsedPorts(instance_name, child, used))
927 my_dict = dict((node, {}) for node in self._config_data.nodes)
928 for instance in self._config_data.instances.itervalues():
929 for disk in instance.disks:
930 duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
931 for (node, minor), instance in self._temporary_drbds.iteritems():
932 if minor in my_dict[node] and my_dict[node][minor] != instance:
933 duplicates.append((node, minor, instance, my_dict[node][minor]))
935 my_dict[node][minor] = instance
936 return my_dict, duplicates
938 @locking.ssynchronized(_config_lock)
939 def ComputeDRBDMap(self):
940 """Compute the used DRBD minor/nodes.
942 This is just a wrapper over L{_UnlockedComputeDRBDMap}.
944 @return: dictionary of node_name: dict of minor: instance_name;
945 the returned dict will have all the nodes in it (even if with
949 d_map, duplicates = self._UnlockedComputeDRBDMap()
951 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
955 @locking.ssynchronized(_config_lock)
956 def AllocateDRBDMinor(self, nodes, instance):
957 """Allocate a drbd minor.
959 The free minor will be automatically computed from the existing
960 devices. A node can be given multiple times in order to allocate
961 multiple minors. The result is the list of minors, in the same
962 order as the passed nodes.
964 @type instance: string
965 @param instance: the instance for which we allocate minors
968 assert isinstance(instance, basestring), \
969 "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
971 d_map, duplicates = self._UnlockedComputeDRBDMap()
973 raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
979 # no minors used, we can start at 0
982 self._temporary_drbds[(nname, 0)] = instance
986 ffree = utils.FirstFree(keys)
988 # return the next minor
989 # TODO: implement high-limit check
993 # double-check minor against current instances
994 assert minor not in d_map[nname], \
995 ("Attempt to reuse allocated DRBD minor %d on node %s,"
996 " already allocated to instance %s" %
997 (minor, nname, d_map[nname][minor]))
998 ndata[minor] = instance
999 # double-check minor against reservation
1000 r_key = (nname, minor)
1001 assert r_key not in self._temporary_drbds, \
1002 ("Attempt to reuse reserved DRBD minor %d on node %s,"
1003 " reserved for instance %s" %
1004 (minor, nname, self._temporary_drbds[r_key]))
1005 self._temporary_drbds[r_key] = instance
1006 result.append(minor)
1007 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1011 def _UnlockedReleaseDRBDMinors(self, instance):
1012 """Release temporary drbd minors allocated for a given instance.
1014 @type instance: string
1015 @param instance: the instance for which temporary minors should be
1019 assert isinstance(instance, basestring), \
1020 "Invalid argument passed to ReleaseDRBDMinors"
1021 for key, name in self._temporary_drbds.items():
1022 if name == instance:
1023 del self._temporary_drbds[key]
1025 @locking.ssynchronized(_config_lock)
1026 def ReleaseDRBDMinors(self, instance):
1027 """Release temporary drbd minors allocated for a given instance.
1029 This should be called on the error paths, on the success paths
1030 it's automatically called by the ConfigWriter add and update
1033 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1035 @type instance: string
1036 @param instance: the instance for which temporary minors should be
1040 self._UnlockedReleaseDRBDMinors(instance)
1042 @locking.ssynchronized(_config_lock, shared=1)
1043 def GetConfigVersion(self):
1044 """Get the configuration version.
1046 @return: Config version
1049 return self._config_data.version
1051 @locking.ssynchronized(_config_lock, shared=1)
1052 def GetClusterName(self):
1053 """Get cluster name.
1055 @return: Cluster name
1058 return self._config_data.cluster.cluster_name
1060 @locking.ssynchronized(_config_lock, shared=1)
1061 def GetMasterNode(self):
1062 """Get the hostname of the master node for this cluster.
1064 @return: Master hostname
1067 return self._config_data.cluster.master_node
1069 @locking.ssynchronized(_config_lock, shared=1)
1070 def GetMasterIP(self):
1071 """Get the IP of the master node for this cluster.
1076 return self._config_data.cluster.master_ip
1078 @locking.ssynchronized(_config_lock, shared=1)
1079 def GetMasterNetdev(self):
1080 """Get the master network device for this cluster.
1083 return self._config_data.cluster.master_netdev
1085 @locking.ssynchronized(_config_lock, shared=1)
1086 def GetMasterNetmask(self):
1087 """Get the netmask of the master node for this cluster.
1090 return self._config_data.cluster.master_netmask
1092 @locking.ssynchronized(_config_lock, shared=1)
1093 def GetUseExternalMipScript(self):
1094 """Get flag representing whether to use the external master IP setup script.
1097 return self._config_data.cluster.use_external_mip_script
1099 @locking.ssynchronized(_config_lock, shared=1)
1100 def GetFileStorageDir(self):
1101 """Get the file storage dir for this cluster.
1104 return self._config_data.cluster.file_storage_dir
1106 @locking.ssynchronized(_config_lock, shared=1)
1107 def GetSharedFileStorageDir(self):
1108 """Get the shared file storage dir for this cluster.
1111 return self._config_data.cluster.shared_file_storage_dir
1113 @locking.ssynchronized(_config_lock, shared=1)
1114 def GetHypervisorType(self):
1115 """Get the hypervisor type for this cluster.
1118 return self._config_data.cluster.enabled_hypervisors[0]
1120 @locking.ssynchronized(_config_lock, shared=1)
1121 def GetHostKey(self):
1122 """Return the rsa hostkey from the config.
1125 @return: the rsa hostkey
1128 return self._config_data.cluster.rsahostkeypub
1130 @locking.ssynchronized(_config_lock, shared=1)
1131 def GetDefaultIAllocator(self):
1132 """Get the default instance allocator for this cluster.
1135 return self._config_data.cluster.default_iallocator
1137 @locking.ssynchronized(_config_lock, shared=1)
1138 def GetPrimaryIPFamily(self):
1139 """Get cluster primary ip family.
1141 @return: primary ip family
1144 return self._config_data.cluster.primary_ip_family
1146 @locking.ssynchronized(_config_lock, shared=1)
1147 def GetMasterNetworkParameters(self):
1148 """Get network parameters of the master node.
1150 @rtype: L{object.MasterNetworkParameters}
1151 @return: network parameters of the master node
1154 cluster = self._config_data.cluster
1155 result = objects.MasterNetworkParameters(
1156 name=cluster.master_node, ip=cluster.master_ip,
1157 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1158 ip_family=cluster.primary_ip_family)
1162 @locking.ssynchronized(_config_lock)
1163 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1164 """Add a node group to the configuration.
1166 This method calls group.UpgradeConfig() to fill any missing attributes
1167 according to their default values.
1169 @type group: L{objects.NodeGroup}
1170 @param group: the NodeGroup object to add
1172 @param ec_id: unique id for the job to use when creating a missing UUID
1173 @type check_uuid: bool
1174 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1175 it does, ensure that it does not exist in the
1176 configuration already
1179 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1182 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1183 """Add a node group to the configuration.
1186 logging.info("Adding node group %s to configuration", group.name)
1188 # Some code might need to add a node group with a pre-populated UUID
1189 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1190 # the "does this UUID" exist already check.
1192 self._EnsureUUID(group, ec_id)
1195 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1196 except errors.OpPrereqError:
1199 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1200 " node group (UUID: %s)" %
1201 (group.name, existing_uuid),
1202 errors.ECODE_EXISTS)
1205 group.ctime = group.mtime = time.time()
1206 group.UpgradeConfig()
1208 self._config_data.nodegroups[group.uuid] = group
1209 self._config_data.cluster.serial_no += 1
1211 @locking.ssynchronized(_config_lock)
1212 def RemoveNodeGroup(self, group_uuid):
1213 """Remove a node group from the configuration.
1215 @type group_uuid: string
1216 @param group_uuid: the UUID of the node group to remove
1219 logging.info("Removing node group %s from configuration", group_uuid)
1221 if group_uuid not in self._config_data.nodegroups:
1222 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1224 assert len(self._config_data.nodegroups) != 1, \
1225 "Group '%s' is the only group, cannot be removed" % group_uuid
1227 del self._config_data.nodegroups[group_uuid]
1228 self._config_data.cluster.serial_no += 1
1231 def _UnlockedLookupNodeGroup(self, target):
1232 """Lookup a node group's UUID.
1234 @type target: string or None
1235 @param target: group name or UUID or None to look for the default
1237 @return: nodegroup UUID
1238 @raises errors.OpPrereqError: when the target group cannot be found
1242 if len(self._config_data.nodegroups) != 1:
1243 raise errors.OpPrereqError("More than one node group exists. Target"
1244 " group must be specified explicitly.")
1246 return self._config_data.nodegroups.keys()[0]
1247 if target in self._config_data.nodegroups:
1249 for nodegroup in self._config_data.nodegroups.values():
1250 if nodegroup.name == target:
1251 return nodegroup.uuid
1252 raise errors.OpPrereqError("Node group '%s' not found" % target,
1255 @locking.ssynchronized(_config_lock, shared=1)
1256 def LookupNodeGroup(self, target):
1257 """Lookup a node group's UUID.
1259 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1261 @type target: string or None
1262 @param target: group name or UUID or None to look for the default
1264 @return: nodegroup UUID
1267 return self._UnlockedLookupNodeGroup(target)
1269 def _UnlockedGetNodeGroup(self, uuid):
1270 """Lookup a node group.
1273 @param uuid: group UUID
1274 @rtype: L{objects.NodeGroup} or None
1275 @return: nodegroup object, or None if not found
1278 if uuid not in self._config_data.nodegroups:
1281 return self._config_data.nodegroups[uuid]
1283 @locking.ssynchronized(_config_lock, shared=1)
1284 def GetNodeGroup(self, uuid):
1285 """Lookup a node group.
1288 @param uuid: group UUID
1289 @rtype: L{objects.NodeGroup} or None
1290 @return: nodegroup object, or None if not found
1293 return self._UnlockedGetNodeGroup(uuid)
1295 @locking.ssynchronized(_config_lock, shared=1)
1296 def GetAllNodeGroupsInfo(self):
1297 """Get the configuration of all node groups.
1300 return dict(self._config_data.nodegroups)
1302 @locking.ssynchronized(_config_lock, shared=1)
1303 def GetNodeGroupList(self):
1304 """Get a list of node groups.
1307 return self._config_data.nodegroups.keys()
1309 @locking.ssynchronized(_config_lock, shared=1)
1310 def GetNodeGroupMembersByNodes(self, nodes):
1311 """Get nodes which are member in the same nodegroups as the given nodes.
1314 ngfn = lambda node_name: self._UnlockedGetNodeInfo(node_name).group
1315 return frozenset(member_name
1316 for node_name in nodes
1318 self._UnlockedGetNodeGroup(ngfn(node_name)).members)
1320 @locking.ssynchronized(_config_lock, shared=1)
1321 def GetMultiNodeGroupInfo(self, group_uuids):
1322 """Get the configuration of multiple node groups.
1324 @param group_uuids: List of node group UUIDs
1326 @return: List of tuples of (group_uuid, group_info)
1329 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1331 @locking.ssynchronized(_config_lock)
1332 def AddInstance(self, instance, ec_id):
1333 """Add an instance to the config.
1335 This should be used after creating a new instance.
1337 @type instance: L{objects.Instance}
1338 @param instance: the instance object
1341 if not isinstance(instance, objects.Instance):
1342 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1344 if instance.disk_template != constants.DT_DISKLESS:
1345 all_lvs = instance.MapLVsByNode()
1346 logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
1348 all_macs = self._AllMACs()
1349 for nic in instance.nics:
1350 if nic.mac in all_macs:
1351 raise errors.ConfigurationError("Cannot add instance %s:"
1352 " MAC address '%s' already in use." %
1353 (instance.name, nic.mac))
1355 self._EnsureUUID(instance, ec_id)
1357 instance.serial_no = 1
1358 instance.ctime = instance.mtime = time.time()
1359 self._config_data.instances[instance.name] = instance
1360 self._config_data.cluster.serial_no += 1
1361 self._UnlockedReleaseDRBDMinors(instance.name)
1364 def _EnsureUUID(self, item, ec_id):
1365 """Ensures a given object has a valid UUID.
1367 @param item: the instance or node to be checked
1368 @param ec_id: the execution context id for the uuid reservation
1372 item.uuid = self._GenerateUniqueID(ec_id)
1373 elif item.uuid in self._AllIDs(include_temporary=True):
1374 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1375 " in use" % (item.name, item.uuid))
1377 def _SetInstanceStatus(self, instance_name, status):
1378 """Set the instance's status to a given value.
1381 assert status in constants.ADMINST_ALL, \
1382 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1384 if instance_name not in self._config_data.instances:
1385 raise errors.ConfigurationError("Unknown instance '%s'" %
1387 instance = self._config_data.instances[instance_name]
1388 if instance.admin_state != status:
1389 instance.admin_state = status
1390 instance.serial_no += 1
1391 instance.mtime = time.time()
1394 @locking.ssynchronized(_config_lock)
1395 def MarkInstanceUp(self, instance_name):
1396 """Mark the instance status to up in the config.
1399 self._SetInstanceStatus(instance_name, constants.ADMINST_UP)
1401 @locking.ssynchronized(_config_lock)
1402 def MarkInstanceOffline(self, instance_name):
1403 """Mark the instance status to down in the config.
1406 self._SetInstanceStatus(instance_name, constants.ADMINST_OFFLINE)
1408 @locking.ssynchronized(_config_lock)
1409 def RemoveInstance(self, instance_name):
1410 """Remove the instance from the configuration.
1413 if instance_name not in self._config_data.instances:
1414 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1416 # If a network port has been allocated to the instance,
1417 # return it to the pool of free ports.
1418 inst = self._config_data.instances[instance_name]
1419 network_port = getattr(inst, "network_port", None)
1420 if network_port is not None:
1421 self._config_data.cluster.tcpudp_port_pool.add(network_port)
1423 del self._config_data.instances[instance_name]
1424 self._config_data.cluster.serial_no += 1
1427 @locking.ssynchronized(_config_lock)
1428 def RenameInstance(self, old_name, new_name):
1429 """Rename an instance.
1431 This needs to be done in ConfigWriter and not by RemoveInstance
1432 combined with AddInstance as only we can guarantee an atomic
1436 if old_name not in self._config_data.instances:
1437 raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1439 # Operate on a copy to not loose instance object in case of a failure
1440 inst = self._config_data.instances[old_name].Copy()
1441 inst.name = new_name
1443 for (idx, disk) in enumerate(inst.disks):
1444 if disk.dev_type == constants.LD_FILE:
1445 # rename the file paths in logical and physical id
1446 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1447 disk.logical_id = (disk.logical_id[0],
1448 utils.PathJoin(file_storage_dir, inst.name,
1450 disk.physical_id = disk.logical_id
1452 # Actually replace instance object
1453 del self._config_data.instances[old_name]
1454 self._config_data.instances[inst.name] = inst
1456 # Force update of ssconf files
1457 self._config_data.cluster.serial_no += 1
1461 @locking.ssynchronized(_config_lock)
1462 def MarkInstanceDown(self, instance_name):
1463 """Mark the status of an instance to down in the configuration.
1466 self._SetInstanceStatus(instance_name, constants.ADMINST_DOWN)
1468 def _UnlockedGetInstanceList(self):
1469 """Get the list of instances.
1471 This function is for internal use, when the config lock is already held.
1474 return self._config_data.instances.keys()
1476 @locking.ssynchronized(_config_lock, shared=1)
1477 def GetInstanceList(self):
1478 """Get the list of instances.
1480 @return: array of instances, ex. ['instance2.example.com',
1481 'instance1.example.com']
1484 return self._UnlockedGetInstanceList()
1486 def ExpandInstanceName(self, short_name):
1487 """Attempt to expand an incomplete instance name.
1490 # Locking is done in L{ConfigWriter.GetInstanceList}
1491 return _MatchNameComponentIgnoreCase(short_name, self.GetInstanceList())
1493 def _UnlockedGetInstanceInfo(self, instance_name):
1494 """Returns information about an instance.
1496 This function is for internal use, when the config lock is already held.
1499 if instance_name not in self._config_data.instances:
1502 return self._config_data.instances[instance_name]
1504 @locking.ssynchronized(_config_lock, shared=1)
1505 def GetInstanceInfo(self, instance_name):
1506 """Returns information about an instance.
1508 It takes the information from the configuration file. Other information of
1509 an instance are taken from the live systems.
1511 @param instance_name: name of the instance, e.g.
1512 I{instance1.example.com}
1514 @rtype: L{objects.Instance}
1515 @return: the instance object
1518 return self._UnlockedGetInstanceInfo(instance_name)
1520 @locking.ssynchronized(_config_lock, shared=1)
1521 def GetInstanceNodeGroups(self, instance_name, primary_only=False):
1522 """Returns set of node group UUIDs for instance's nodes.
1527 instance = self._UnlockedGetInstanceInfo(instance_name)
1529 raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
1532 nodes = [instance.primary_node]
1534 nodes = instance.all_nodes
1536 return frozenset(self._UnlockedGetNodeInfo(node_name).group
1537 for node_name in nodes)
1539 @locking.ssynchronized(_config_lock, shared=1)
1540 def GetMultiInstanceInfo(self, instances):
1541 """Get the configuration of multiple instances.
1543 @param instances: list of instance names
1545 @return: list of tuples (instance, instance_info), where
1546 instance_info is what would GetInstanceInfo return for the
1547 node, while keeping the original order
1550 return [(name, self._UnlockedGetInstanceInfo(name)) for name in instances]
1552 @locking.ssynchronized(_config_lock, shared=1)
1553 def GetAllInstancesInfo(self):
1554 """Get the configuration of all instances.
1557 @return: dict of (instance, instance_info), where instance_info is what
1558 would GetInstanceInfo return for the node
1561 my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1562 for instance in self._UnlockedGetInstanceList()])
1565 @locking.ssynchronized(_config_lock, shared=1)
1566 def GetInstancesInfoByFilter(self, filter_fn):
1567 """Get instance configuration with a filter.
1569 @type filter_fn: callable
1570 @param filter_fn: Filter function receiving instance object as parameter,
1571 returning boolean. Important: this function is called while the
1572 configuration locks is held. It must not do any complex work or call
1573 functions potentially leading to a deadlock. Ideally it doesn't call any
1574 other functions and just compares instance attributes.
1577 return dict((name, inst)
1578 for (name, inst) in self._config_data.instances.items()
1581 @locking.ssynchronized(_config_lock)
1582 def AddNode(self, node, ec_id):
1583 """Add a node to the configuration.
1585 @type node: L{objects.Node}
1586 @param node: a Node instance
1589 logging.info("Adding node %s to configuration", node.name)
1591 self._EnsureUUID(node, ec_id)
1594 node.ctime = node.mtime = time.time()
1595 self._UnlockedAddNodeToGroup(node.name, node.group)
1596 self._config_data.nodes[node.name] = node
1597 self._config_data.cluster.serial_no += 1
1600 @locking.ssynchronized(_config_lock)
1601 def RemoveNode(self, node_name):
1602 """Remove a node from the configuration.
1605 logging.info("Removing node %s from configuration", node_name)
1607 if node_name not in self._config_data.nodes:
1608 raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1610 self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1611 del self._config_data.nodes[node_name]
1612 self._config_data.cluster.serial_no += 1
1615 def ExpandNodeName(self, short_name):
1616 """Attempt to expand an incomplete node name.
1619 # Locking is done in L{ConfigWriter.GetNodeList}
1620 return _MatchNameComponentIgnoreCase(short_name, self.GetNodeList())
1622 def _UnlockedGetNodeInfo(self, node_name):
1623 """Get the configuration of a node, as stored in the config.
1625 This function is for internal use, when the config lock is already
1628 @param node_name: the node name, e.g. I{node1.example.com}
1630 @rtype: L{objects.Node}
1631 @return: the node object
1634 if node_name not in self._config_data.nodes:
1637 return self._config_data.nodes[node_name]
1639 @locking.ssynchronized(_config_lock, shared=1)
1640 def GetNodeInfo(self, node_name):
1641 """Get the configuration of a node, as stored in the config.
1643 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1645 @param node_name: the node name, e.g. I{node1.example.com}
1647 @rtype: L{objects.Node}
1648 @return: the node object
1651 return self._UnlockedGetNodeInfo(node_name)
1653 @locking.ssynchronized(_config_lock, shared=1)
1654 def GetNodeInstances(self, node_name):
1655 """Get the instances of a node, as stored in the config.
1657 @param node_name: the node name, e.g. I{node1.example.com}
1659 @rtype: (list, list)
1660 @return: a tuple with two lists: the primary and the secondary instances
1665 for inst in self._config_data.instances.values():
1666 if inst.primary_node == node_name:
1667 pri.append(inst.name)
1668 if node_name in inst.secondary_nodes:
1669 sec.append(inst.name)
1672 @locking.ssynchronized(_config_lock, shared=1)
1673 def GetNodeGroupInstances(self, uuid, primary_only=False):
1674 """Get the instances of a node group.
1676 @param uuid: Node group UUID
1677 @param primary_only: Whether to only consider primary nodes
1679 @return: List of instance names in node group
1683 nodes_fn = lambda inst: [inst.primary_node]
1685 nodes_fn = lambda inst: inst.all_nodes
1687 return frozenset(inst.name
1688 for inst in self._config_data.instances.values()
1689 for node_name in nodes_fn(inst)
1690 if self._UnlockedGetNodeInfo(node_name).group == uuid)
1692 def _UnlockedGetNodeList(self):
1693 """Return the list of nodes which are in the configuration.
1695 This function is for internal use, when the config lock is already
1701 return self._config_data.nodes.keys()
1703 @locking.ssynchronized(_config_lock, shared=1)
1704 def GetNodeList(self):
1705 """Return the list of nodes which are in the configuration.
1708 return self._UnlockedGetNodeList()
1710 def _UnlockedGetOnlineNodeList(self):
1711 """Return the list of nodes which are online.
1714 all_nodes = [self._UnlockedGetNodeInfo(node)
1715 for node in self._UnlockedGetNodeList()]
1716 return [node.name for node in all_nodes if not node.offline]
1718 @locking.ssynchronized(_config_lock, shared=1)
1719 def GetOnlineNodeList(self):
1720 """Return the list of nodes which are online.
1723 return self._UnlockedGetOnlineNodeList()
1725 @locking.ssynchronized(_config_lock, shared=1)
1726 def GetVmCapableNodeList(self):
1727 """Return the list of nodes which are not vm capable.
1730 all_nodes = [self._UnlockedGetNodeInfo(node)
1731 for node in self._UnlockedGetNodeList()]
1732 return [node.name for node in all_nodes if node.vm_capable]
1734 @locking.ssynchronized(_config_lock, shared=1)
1735 def GetNonVmCapableNodeList(self):
1736 """Return the list of nodes which are not vm capable.
1739 all_nodes = [self._UnlockedGetNodeInfo(node)
1740 for node in self._UnlockedGetNodeList()]
1741 return [node.name for node in all_nodes if not node.vm_capable]
1743 @locking.ssynchronized(_config_lock, shared=1)
1744 def GetMultiNodeInfo(self, nodes):
1745 """Get the configuration of multiple nodes.
1747 @param nodes: list of node names
1749 @return: list of tuples of (node, node_info), where node_info is
1750 what would GetNodeInfo return for the node, in the original
1754 return [(name, self._UnlockedGetNodeInfo(name)) for name in nodes]
1756 @locking.ssynchronized(_config_lock, shared=1)
1757 def GetAllNodesInfo(self):
1758 """Get the configuration of all nodes.
1761 @return: dict of (node, node_info), where node_info is what
1762 would GetNodeInfo return for the node
1765 return self._UnlockedGetAllNodesInfo()
1767 def _UnlockedGetAllNodesInfo(self):
1768 """Gets configuration of all nodes.
1770 @note: See L{GetAllNodesInfo}
1773 return dict([(node, self._UnlockedGetNodeInfo(node))
1774 for node in self._UnlockedGetNodeList()])
1776 @locking.ssynchronized(_config_lock, shared=1)
1777 def GetNodeGroupsFromNodes(self, nodes):
1778 """Returns groups for a list of nodes.
1780 @type nodes: list of string
1781 @param nodes: List of node names
1785 return frozenset(self._UnlockedGetNodeInfo(name).group for name in nodes)
1787 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1788 """Get the number of current and maximum desired and possible candidates.
1790 @type exceptions: list
1791 @param exceptions: if passed, list of nodes that should be ignored
1793 @return: tuple of (current, desired and possible, possible)
1796 mc_now = mc_should = mc_max = 0
1797 for node in self._config_data.nodes.values():
1798 if exceptions and node.name in exceptions:
1800 if not (node.offline or node.drained) and node.master_capable:
1802 if node.master_candidate:
1804 mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1805 return (mc_now, mc_should, mc_max)
1807 @locking.ssynchronized(_config_lock, shared=1)
1808 def GetMasterCandidateStats(self, exceptions=None):
1809 """Get the number of current and maximum possible candidates.
1811 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1813 @type exceptions: list
1814 @param exceptions: if passed, list of nodes that should be ignored
1816 @return: tuple of (current, max)
1819 return self._UnlockedGetMasterCandidateStats(exceptions)
1821 @locking.ssynchronized(_config_lock)
1822 def MaintainCandidatePool(self, exceptions):
1823 """Try to grow the candidate pool to the desired size.
1825 @type exceptions: list
1826 @param exceptions: if passed, list of nodes that should be ignored
1828 @return: list with the adjusted nodes (L{objects.Node} instances)
1831 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1834 node_list = self._config_data.nodes.keys()
1835 random.shuffle(node_list)
1836 for name in node_list:
1837 if mc_now >= mc_max:
1839 node = self._config_data.nodes[name]
1840 if (node.master_candidate or node.offline or node.drained or
1841 node.name in exceptions or not node.master_capable):
1843 mod_list.append(node)
1844 node.master_candidate = True
1847 if mc_now != mc_max:
1848 # this should not happen
1849 logging.warning("Warning: MaintainCandidatePool didn't manage to"
1850 " fill the candidate pool (%d/%d)", mc_now, mc_max)
1852 self._config_data.cluster.serial_no += 1
1857 def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1858 """Add a given node to the specified group.
1861 if nodegroup_uuid not in self._config_data.nodegroups:
1862 # This can happen if a node group gets deleted between its lookup and
1863 # when we're adding the first node to it, since we don't keep a lock in
1864 # the meantime. It's ok though, as we'll fail cleanly if the node group
1865 # is not found anymore.
1866 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1867 if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1868 self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1870 def _UnlockedRemoveNodeFromGroup(self, node):
1871 """Remove a given node from its group.
1874 nodegroup = node.group
1875 if nodegroup not in self._config_data.nodegroups:
1876 logging.warning("Warning: node '%s' has unknown node group '%s'"
1877 " (while being removed from it)", node.name, nodegroup)
1878 nodegroup_obj = self._config_data.nodegroups[nodegroup]
1879 if node.name not in nodegroup_obj.members:
1880 logging.warning("Warning: node '%s' not a member of its node group '%s'"
1881 " (while being removed from it)", node.name, nodegroup)
1883 nodegroup_obj.members.remove(node.name)
1885 @locking.ssynchronized(_config_lock)
1886 def AssignGroupNodes(self, mods):
1887 """Changes the group of a number of nodes.
1889 @type mods: list of tuples; (node name, new group UUID)
1890 @param mods: Node membership modifications
1893 groups = self._config_data.nodegroups
1894 nodes = self._config_data.nodes
1898 # Try to resolve names/UUIDs first
1899 for (node_name, new_group_uuid) in mods:
1901 node = nodes[node_name]
1903 raise errors.ConfigurationError("Unable to find node '%s'" % node_name)
1905 if node.group == new_group_uuid:
1906 # Node is being assigned to its current group
1907 logging.debug("Node '%s' was assigned to its current group (%s)",
1908 node_name, node.group)
1911 # Try to find current group of node
1913 old_group = groups[node.group]
1915 raise errors.ConfigurationError("Unable to find old group '%s'" %
1918 # Try to find new group for node
1920 new_group = groups[new_group_uuid]
1922 raise errors.ConfigurationError("Unable to find new group '%s'" %
1925 assert node.name in old_group.members, \
1926 ("Inconsistent configuration: node '%s' not listed in members for its"
1927 " old group '%s'" % (node.name, old_group.uuid))
1928 assert node.name not in new_group.members, \
1929 ("Inconsistent configuration: node '%s' already listed in members for"
1930 " its new group '%s'" % (node.name, new_group.uuid))
1932 resmod.append((node, old_group, new_group))
1935 for (node, old_group, new_group) in resmod:
1936 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
1937 "Assigning to current group is not possible"
1939 node.group = new_group.uuid
1941 # Update members of involved groups
1942 if node.name in old_group.members:
1943 old_group.members.remove(node.name)
1944 if node.name not in new_group.members:
1945 new_group.members.append(node.name)
1947 # Update timestamps and serials (only once per node/group object)
1949 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
1953 # Force ssconf update
1954 self._config_data.cluster.serial_no += 1
1958 def _BumpSerialNo(self):
1959 """Bump up the serial number of the config.
1962 self._config_data.serial_no += 1
1963 self._config_data.mtime = time.time()
1965 def _AllUUIDObjects(self):
1966 """Returns all objects with uuid attributes.
1969 return (self._config_data.instances.values() +
1970 self._config_data.nodes.values() +
1971 self._config_data.nodegroups.values() +
1972 [self._config_data.cluster])
1974 def _OpenConfig(self, accept_foreign):
1975 """Read the config data from disk.
1978 raw_data = utils.ReadFile(self._cfg_file)
1981 data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1982 except Exception, err:
1983 raise errors.ConfigurationError(err)
1985 # Make sure the configuration has the right version
1986 _ValidateConfig(data)
1988 if (not hasattr(data, "cluster") or
1989 not hasattr(data.cluster, "rsahostkeypub")):
1990 raise errors.ConfigurationError("Incomplete configuration"
1991 " (missing cluster.rsahostkeypub)")
1993 if data.cluster.master_node != self._my_hostname and not accept_foreign:
1994 msg = ("The configuration denotes node %s as master, while my"
1995 " hostname is %s; opening a foreign configuration is only"
1996 " possible in accept_foreign mode" %
1997 (data.cluster.master_node, self._my_hostname))
1998 raise errors.ConfigurationError(msg)
2000 # Upgrade configuration if needed
2001 data.UpgradeConfig()
2003 self._config_data = data
2004 # reset the last serial as -1 so that the next write will cause
2006 self._last_cluster_serial = -1
2008 # And finally run our (custom) config upgrade sequence
2009 self._UpgradeConfig()
2011 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2013 def _UpgradeConfig(self):
2014 """Run upgrade steps that cannot be done purely in the objects.
2016 This is because some data elements need uniqueness across the
2017 whole configuration, etc.
2019 @warning: this function will call L{_WriteConfig()}, but also
2020 L{DropECReservations} so it needs to be called only from a
2021 "safe" place (the constructor). If one wanted to call it with
2022 the lock held, a DropECReservationUnlocked would need to be
2023 created first, to avoid causing deadlock.
2027 for item in self._AllUUIDObjects():
2028 if item.uuid is None:
2029 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2031 if not self._config_data.nodegroups:
2032 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2033 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2035 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2037 for node in self._config_data.nodes.values():
2039 node.group = self.LookupNodeGroup(None)
2041 # This is technically *not* an upgrade, but needs to be done both when
2042 # nodegroups are being added, and upon normally loading the config,
2043 # because the members list of a node group is discarded upon
2044 # serializing/deserializing the object.
2045 self._UnlockedAddNodeToGroup(node.name, node.group)
2048 # This is ok even if it acquires the internal lock, as _UpgradeConfig is
2049 # only called at config init time, without the lock held
2050 self.DropECReservations(_UPGRADE_CONFIG_JID)
2052 def _DistributeConfig(self, feedback_fn):
2053 """Distribute the configuration to the other nodes.
2055 Currently, this only copies the configuration file. In the future,
2056 it could be used to encapsulate the 2/3-phase update mechanism.
2066 myhostname = self._my_hostname
2067 # we can skip checking whether _UnlockedGetNodeInfo returns None
2068 # since the node list comes from _UnlocketGetNodeList, and we are
2069 # called with the lock held, so no modifications should take place
2071 for node_name in self._UnlockedGetNodeList():
2072 if node_name == myhostname:
2074 node_info = self._UnlockedGetNodeInfo(node_name)
2075 if not node_info.master_candidate:
2077 node_list.append(node_info.name)
2078 addr_list.append(node_info.primary_ip)
2080 # TODO: Use dedicated resolver talking to config writer for name resolution
2082 self._GetRpc(addr_list).call_upload_file(node_list, self._cfg_file)
2083 for to_node, to_result in result.items():
2084 msg = to_result.fail_msg
2086 msg = ("Copy of file %s to node %s failed: %s" %
2087 (self._cfg_file, to_node, msg))
2097 def _WriteConfig(self, destination=None, feedback_fn=None):
2098 """Write the configuration data to persistent storage.
2101 assert feedback_fn is None or callable(feedback_fn)
2103 # Warn on config errors, but don't abort the save - the
2104 # configuration has already been modified, and we can't revert;
2105 # the best we can do is to warn the user and save as is, leaving
2106 # recovery to the user
2107 config_errors = self._UnlockedVerifyConfig()
2109 errmsg = ("Configuration data is not consistent: %s" %
2110 (utils.CommaJoin(config_errors)))
2111 logging.critical(errmsg)
2115 if destination is None:
2116 destination = self._cfg_file
2117 self._BumpSerialNo()
2118 txt = serializer.Dump(self._config_data.ToDict())
2120 getents = self._getents()
2122 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2123 close=False, gid=getents.confd_gid, mode=0640)
2124 except errors.LockError:
2125 raise errors.ConfigurationError("The configuration file has been"
2126 " modified since the last write, cannot"
2129 self._cfg_id = utils.GetFileID(fd=fd)
2133 self.write_count += 1
2135 # and redistribute the config file to master candidates
2136 self._DistributeConfig(feedback_fn)
2138 # Write ssconf files on all nodes (including locally)
2139 if self._last_cluster_serial < self._config_data.cluster.serial_no:
2140 if not self._offline:
2141 result = self._GetRpc(None).call_write_ssconf_files(
2142 self._UnlockedGetOnlineNodeList(),
2143 self._UnlockedGetSsconfValues())
2145 for nname, nresu in result.items():
2146 msg = nresu.fail_msg
2148 errmsg = ("Error while uploading ssconf files to"
2149 " node %s: %s" % (nname, msg))
2150 logging.warning(errmsg)
2155 self._last_cluster_serial = self._config_data.cluster.serial_no
2157 def _UnlockedGetSsconfValues(self):
2158 """Return the values needed by ssconf.
2161 @return: a dictionary with keys the ssconf names and values their
2166 instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
2167 node_names = utils.NiceSort(self._UnlockedGetNodeList())
2168 node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
2169 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2170 for ninfo in node_info]
2171 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2172 for ninfo in node_info]
2174 instance_data = fn(instance_names)
2175 off_data = fn(node.name for node in node_info if node.offline)
2176 on_data = fn(node.name for node in node_info if not node.offline)
2177 mc_data = fn(node.name for node in node_info if node.master_candidate)
2178 mc_ips_data = fn(node.primary_ip for node in node_info
2179 if node.master_candidate)
2180 node_data = fn(node_names)
2181 node_pri_ips_data = fn(node_pri_ips)
2182 node_snd_ips_data = fn(node_snd_ips)
2184 cluster = self._config_data.cluster
2185 cluster_tags = fn(cluster.GetTags())
2187 hypervisor_list = fn(cluster.enabled_hypervisors)
2189 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2191 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2192 self._config_data.nodegroups.values()]
2193 nodegroups_data = fn(utils.NiceSort(nodegroups))
2194 networks = ["%s %s" % (net.uuid, net.name) for net in
2195 self._config_data.networks.values()]
2196 networks_data = fn(utils.NiceSort(networks))
2199 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2200 constants.SS_CLUSTER_TAGS: cluster_tags,
2201 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2202 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2203 constants.SS_MASTER_CANDIDATES: mc_data,
2204 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2205 constants.SS_MASTER_IP: cluster.master_ip,
2206 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2207 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2208 constants.SS_MASTER_NODE: cluster.master_node,
2209 constants.SS_NODE_LIST: node_data,
2210 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2211 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2212 constants.SS_OFFLINE_NODES: off_data,
2213 constants.SS_ONLINE_NODES: on_data,
2214 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2215 constants.SS_INSTANCE_LIST: instance_data,
2216 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2217 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2218 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2219 constants.SS_UID_POOL: uid_pool,
2220 constants.SS_NODEGROUPS: nodegroups_data,
2221 constants.SS_NETWORKS: networks_data,
2223 bad_values = [(k, v) for k, v in ssconf_values.items()
2224 if not isinstance(v, (str, basestring))]
2226 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2227 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2228 " values: %s" % err)
2229 return ssconf_values
2231 @locking.ssynchronized(_config_lock, shared=1)
2232 def GetSsconfValues(self):
2233 """Wrapper using lock around _UnlockedGetSsconf().
2236 return self._UnlockedGetSsconfValues()
2238 @locking.ssynchronized(_config_lock, shared=1)
2239 def GetVGName(self):
2240 """Return the volume group name.
2243 return self._config_data.cluster.volume_group_name
2245 @locking.ssynchronized(_config_lock)
2246 def SetVGName(self, vg_name):
2247 """Set the volume group name.
2250 self._config_data.cluster.volume_group_name = vg_name
2251 self._config_data.cluster.serial_no += 1
2254 @locking.ssynchronized(_config_lock, shared=1)
2255 def GetDRBDHelper(self):
2256 """Return DRBD usermode helper.
2259 return self._config_data.cluster.drbd_usermode_helper
2261 @locking.ssynchronized(_config_lock)
2262 def SetDRBDHelper(self, drbd_helper):
2263 """Set DRBD usermode helper.
2266 self._config_data.cluster.drbd_usermode_helper = drbd_helper
2267 self._config_data.cluster.serial_no += 1
2270 @locking.ssynchronized(_config_lock, shared=1)
2271 def GetMACPrefix(self):
2272 """Return the mac prefix.
2275 return self._config_data.cluster.mac_prefix
2277 @locking.ssynchronized(_config_lock, shared=1)
2278 def GetClusterInfo(self):
2279 """Returns information about the cluster
2281 @rtype: L{objects.Cluster}
2282 @return: the cluster object
2285 return self._config_data.cluster
2287 @locking.ssynchronized(_config_lock, shared=1)
2288 def HasAnyDiskOfType(self, dev_type):
2289 """Check if in there is at disk of the given type in the configuration.
2292 return self._config_data.HasAnyDiskOfType(dev_type)
2294 @locking.ssynchronized(_config_lock)
2295 def Update(self, target, feedback_fn):
2296 """Notify function to be called after updates.
2298 This function must be called when an object (as returned by
2299 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
2300 caller wants the modifications saved to the backing store. Note
2301 that all modified objects will be saved, but the target argument
2302 is the one the caller wants to ensure that it's saved.
2304 @param target: an instance of either L{objects.Cluster},
2305 L{objects.Node} or L{objects.Instance} which is existing in
2307 @param feedback_fn: Callable feedback function
2310 if self._config_data is None:
2311 raise errors.ProgrammerError("Configuration file not read,"
2313 update_serial = False
2314 if isinstance(target, objects.Cluster):
2315 test = target == self._config_data.cluster
2316 elif isinstance(target, objects.Node):
2317 test = target in self._config_data.nodes.values()
2318 update_serial = True
2319 elif isinstance(target, objects.Instance):
2320 test = target in self._config_data.instances.values()
2321 elif isinstance(target, objects.NodeGroup):
2322 test = target in self._config_data.nodegroups.values()
2323 elif isinstance(target, objects.Network):
2324 test = target in self._config_data.networks.values()
2326 raise errors.ProgrammerError("Invalid object type (%s) passed to"
2327 " ConfigWriter.Update" % type(target))
2329 raise errors.ConfigurationError("Configuration updated since object"
2330 " has been read or unknown object")
2331 target.serial_no += 1
2332 target.mtime = now = time.time()
2335 # for node updates, we need to increase the cluster serial too
2336 self._config_data.cluster.serial_no += 1
2337 self._config_data.cluster.mtime = now
2339 if isinstance(target, objects.Instance):
2340 self._UnlockedReleaseDRBDMinors(target.name)
2342 self._WriteConfig(feedback_fn=feedback_fn)
2344 @locking.ssynchronized(_config_lock)
2345 def DropECReservations(self, ec_id):
2346 """Drop per-execution-context reservations
2349 for rm in self._all_rms:
2350 rm.DropECReservations(ec_id)
2352 @locking.ssynchronized(_config_lock, shared=1)
2353 def GetAllNetworksInfo(self):
2354 """Get the configuration of all networks
2357 return dict(self._config_data.networks)
2359 def _UnlockedGetNetworkList(self):
2360 """Get the list of networks.
2362 This function is for internal use, when the config lock is already held.
2365 return self._config_data.networks.keys()
2367 @locking.ssynchronized(_config_lock, shared=1)
2368 def GetNetworkList(self):
2369 """Get the list of networks.
2371 @return: array of networks, ex. ["main", "vlan100", "200]
2374 return self._UnlockedGetNetworkList()
2376 @locking.ssynchronized(_config_lock, shared=1)
2377 def GetNetworkNames(self):
2378 """Get a list of network names
2381 names = [network.name
2382 for network in self._config_data.networks.values()]
2385 def _UnlockedGetNetwork(self, uuid):
2386 """Returns information about a network.
2388 This function is for internal use, when the config lock is already held.
2391 if uuid not in self._config_data.networks:
2394 return self._config_data.networks[uuid]
2396 @locking.ssynchronized(_config_lock, shared=1)
2397 def GetNetwork(self, uuid):
2398 """Returns information about a network.
2400 It takes the information from the configuration file.
2402 @param uuid: UUID of the network
2404 @rtype: L{objects.Network}
2405 @return: the network object
2408 return self._UnlockedGetNetwork(uuid)
2410 @locking.ssynchronized(_config_lock)
2411 def AddNetwork(self, net, ec_id, check_uuid=True):
2412 """Add a network to the configuration.
2414 @type net: L{objects.Network}
2415 @param net: the Network object to add
2417 @param ec_id: unique id for the job to use when creating a missing UUID
2420 self._UnlockedAddNetwork(net, ec_id, check_uuid)
2423 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
2424 """Add a network to the configuration.
2427 logging.info("Adding network %s to configuration", net.name)
2430 self._EnsureUUID(net, ec_id)
2432 existing_uuid = self._UnlockedLookupNetwork(net.name)
2434 raise errors.OpPrereqError("Desired network name '%s' already"
2435 " exists as a network (UUID: %s)" %
2436 (net.name, existing_uuid),
2437 errors.ECODE_EXISTS)
2439 self._config_data.networks[net.uuid] = net
2440 self._config_data.cluster.serial_no += 1
2442 def _UnlockedLookupNetwork(self, target):
2443 """Lookup a network's UUID.
2445 @type target: string
2446 @param target: network name or UUID
2448 @return: network UUID
2449 @raises errors.OpPrereqError: when the target network cannot be found
2452 if target in self._config_data.networks:
2454 for net in self._config_data.networks.values():
2455 if net.name == target:
2459 @locking.ssynchronized(_config_lock, shared=1)
2460 def LookupNetwork(self, target):
2461 """Lookup a network's UUID.
2463 This function is just a wrapper over L{_UnlockedLookupNetwork}.
2465 @type target: string
2466 @param target: network name or UUID
2468 @return: network UUID
2471 return self._UnlockedLookupNetwork(target)
2473 @locking.ssynchronized(_config_lock)
2474 def RemoveNetwork(self, network_uuid):
2475 """Remove a network from the configuration.
2477 @type network_uuid: string
2478 @param network_uuid: the UUID of the network to remove
2481 logging.info("Removing network %s from configuration", network_uuid)
2483 if network_uuid not in self._config_data.networks:
2484 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
2486 del self._config_data.networks[network_uuid]
2487 self._config_data.cluster.serial_no += 1
2490 def _UnlockedGetGroupNetParams(self, net, node):
2491 """Get the netparams (mode, link) of a network.
2493 Get a network's netparams for a given node.
2496 @param net: network name
2498 @param node: node name
2499 @rtype: dict or None
2503 net_uuid = self._UnlockedLookupNetwork(net)
2504 if net_uuid is None:
2507 node_info = self._UnlockedGetNodeInfo(node)
2508 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2509 netparams = nodegroup_info.networks.get(net_uuid, None)
2513 @locking.ssynchronized(_config_lock, shared=1)
2514 def GetGroupNetParams(self, net, node):
2515 """Locking wrapper of _UnlockedGetGroupNetParams()
2518 return self._UnlockedGetGroupNetParams(net, node)
2521 @locking.ssynchronized(_config_lock, shared=1)
2522 def CheckIPInNodeGroup(self, ip, node):
2523 """Check for conflictig IP.
2526 @param ip: ip address
2528 @param node: node name
2529 @rtype: (string, dict) or (None, None)
2530 @return: (network name, netparams)
2535 node_info = self._UnlockedGetNodeInfo(node)
2536 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
2537 for net_uuid in nodegroup_info.networks.keys():
2538 net_info = self._UnlockedGetNetwork(net_uuid)
2539 pool = network.AddressPool(net_info)
2540 if pool._Contains(ip):
2541 return (net_info.name, nodegroup_info.networks[net_uuid])