1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Configuration management for Ganeti
32
33 This module provides the interface to the Ganeti cluster configuration.
34
35 The configuration data is stored on every node but is updated on the master
36 only. After each update, the master distributes the data to the other nodes.
37
38 Currently, the data storage format is JSON. YAML was slow and consuming too
39 much memory.
40
41 """
42
43
44
45
46 import copy
47 import os
48 import random
49 import logging
50 import time
51 import threading
52 import itertools
53
54 from ganeti.config.temporary_reservations import TemporaryReservationManager
55 from ganeti.config.utils import ConfigSync, ConfigManager
56 from ganeti.config.verify import (VerifyType, VerifyNic, VerifyIpolicy,
57 ValidateConfig)
58
59 from ganeti import errors
60 from ganeti import utils
61 from ganeti import constants
62 import ganeti.wconfd as wc
63 from ganeti import objects
64 from ganeti import serializer
65 from ganeti import uidpool
66 from ganeti import netutils
67 from ganeti import runtime
68 from ganeti import pathutils
69 from ganeti import network
70
71
72 -def GetWConfdContext(ec_id, livelock):
73 """Prepare a context for communication with WConfd.
74
75 WConfd needs to know the identity of each caller to properly manage locks and
76 detect job death. This helper function prepares the identity object given a
77 job ID (optional) and a livelock file.
78
79 @type ec_id: int, or None
80 @param ec_id: the job ID or None, if the caller isn't a job
81 @type livelock: L{ganeti.utils.livelock.LiveLock}
82 @param livelock: a livelock object holding the lockfile needed for WConfd
83 @return: the WConfd context
84
85 """
86 if ec_id is None:
87 return (threading.current_thread().getName(),
88 livelock.GetPath(), os.getpid())
89 else:
90 return (ec_id,
91 livelock.GetPath(), os.getpid())
92
95 """A utility function for constructing instances of ConfigWriter.
96
97 It prepares a WConfd context and uses it to create a ConfigWriter instance.
98
99 @type ec_id: int, or None
100 @param ec_id: the job ID or None, if the caller isn't a job
101 @type livelock: L{ganeti.utils.livelock.LiveLock}
102 @param livelock: a livelock object holding the lockfile needed for WConfd
103 @type kwargs: dict
104 @param kwargs: Any additional arguments for the ConfigWriter constructor
105 @rtype: L{ConfigWriter}
106 @return: the ConfigWriter context
107
108 """
109 kwargs['wconfdcontext'] = GetWConfdContext(ec_id, livelock)
110
111
112
113 accept_foreign = kwargs.get('accept_foreign', False)
114 kwargs['wconfd'] = wc.Client(allow_non_master=accept_foreign)
115
116 return ConfigWriter(**kwargs)
117
118
119
120 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
124 """Wrapper around L{utils.text.MatchNameComponent}.
125
126 """
127 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
128
131 """Checks if instance's disks' C{iv_name} attributes are in order.
132
133 @type disks: list of L{objects.Disk}
134 @param disks: List of disks
135 @rtype: list of tuples; (int, string, string)
136 @return: List of wrongly named disks, each tuple contains disk index,
137 expected and actual name
138
139 """
140 result = []
141
142 for (idx, disk) in enumerate(disks):
143 exp_iv_name = "disk/%s" % idx
144 if disk.iv_name != exp_iv_name:
145 result.append((idx, exp_iv_name, disk.iv_name))
146
147 return result
148
151 """Update the C{iv_name} attribute of disks.
152
153 @type disks: list of L{objects.Disk}
154
155 """
156 for (idx, disk) in enumerate(disks):
157 disk.iv_name = "disk/%s" % (base_idx + idx)
158
161 """The interface to the cluster configuration.
162
163 WARNING: The class is no longer thread-safe!
164 Each thread must construct a separate instance.
165
166 @ivar _all_rms: a list of all temporary reservation managers
167
168 Currently the class fulfills 3 main functions:
169 1. lock the configuration for access (monitor)
170 2. reload and write the config if necessary (bridge)
171 3. provide convenient access methods to config data (facade)
172
173 """
174 - def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
175 accept_foreign=False, wconfdcontext=None, wconfd=None):
176 self.write_count = 0
177 self._config_data = None
178 self._SetConfigData(None)
179 self._offline = offline
180 if cfg_file is None:
181 self._cfg_file = pathutils.CLUSTER_CONF_FILE
182 else:
183 self._cfg_file = cfg_file
184 self._getents = _getents
185 self._temporary_ids = TemporaryReservationManager()
186 self._all_rms = [self._temporary_ids]
187
188
189
190
191 self._my_hostname = netutils.Hostname.GetSysName()
192 self._cfg_id = None
193 self._wconfdcontext = wconfdcontext
194 self._wconfd = wconfd
195 self._accept_foreign = accept_foreign
196 self._lock_count = 0
197 self._lock_current_shared = None
198 self._lock_forced = False
199
201 return self._config_data
202
204 self._config_data = None
205
207 self._config_data = cfg
208
210 return self._wconfdcontext
211
212
213 @staticmethod
219
223
224 @ConfigSync(shared=1)
226 """Get the node params populated with cluster defaults.
227
228 @type node: L{objects.Node}
229 @param node: The node we want to know the params for
230 @return: A dict with the filled in node params
231
232 """
233 return self._UnlockedGetNdParams(node)
234
235 @ConfigSync(shared=1)
237 """Get the node groups params populated with cluster defaults.
238
239 @type nodegroup: L{objects.NodeGroup}
240 @param nodegroup: The node group we want to know the params for
241 @return: A dict with the filled in node group params
242
243 """
244 return self._UnlockedGetNdGroupParams(nodegroup)
245
247 """Get the ndparams of the group.
248
249 @type group: L{objects.NodeGroup}
250 @param group: The group we want to know the params for
251 @rtype: dict of str to int
252 @return: A dict with the filled in node group params
253
254 """
255 return self._ConfigData().cluster.FillNDGroup(group)
256
257 @ConfigSync(shared=1)
259 """Get a map of group UUIDs to SSH ports.
260
261 @rtype: dict of str to int
262 @return: a dict mapping the UUIDs to the SSH ports
263
264 """
265 port_map = {}
266 for uuid, group in self._config_data.nodegroups.items():
267 ndparams = self._UnlockedGetNdGroupParams(group)
268 port = ndparams.get(constants.ND_SSH_PORT)
269 port_map[uuid] = port
270 return port_map
271
272 @ConfigSync(shared=1)
284
286 """Return the disks' info for the given instance
287
288 @type inst_uuid: string
289 @param inst_uuid: The UUID of the instance we want to know the disks for
290
291 @rtype: List of L{objects.Disk}
292 @return: A list with all the disks' info
293
294 """
295 instance = self._UnlockedGetInstanceInfo(inst_uuid)
296 if instance is None:
297 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
298
299 return [self._UnlockedGetDiskInfo(disk_uuid)
300 for disk_uuid in instance.disks]
301
302 @ConfigSync(shared=1)
304 """Return the disks' info for the given instance
305
306 This is a simple wrapper over L{_UnlockedGetInstanceDisks}.
307
308 """
309 return self._UnlockedGetInstanceDisks(inst_uuid)
310
333
335 """Attach a disk to an instance.
336
337 @type inst_uuid: string
338 @param inst_uuid: The UUID of the instance object
339 @type disk_uuid: string
340 @param disk_uuid: The UUID of the disk object
341 @type idx: int
342 @param idx: the index of the newly attached disk; if not
343 passed, the disk will be attached as the last one.
344
345 """
346 instance = self._UnlockedGetInstanceInfo(inst_uuid)
347 if instance is None:
348 raise errors.ConfigurationError("Instance %s doesn't exist"
349 % inst_uuid)
350 if disk_uuid not in self._ConfigData().disks:
351 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)
352
353 if idx is None:
354 idx = len(instance.disks)
355 else:
356 if idx < 0:
357 raise IndexError("Not accepting negative indices other than -1")
358 elif idx > len(instance.disks):
359 raise IndexError("Got disk index %s, but there are only %s" %
360 (idx, len(instance.disks)))
361
362
363 for inst in self._ConfigData().instances.values():
364 if disk_uuid in inst.disks:
365 raise errors.ReservationError("Disk %s already attached to instance %s"
366 % (disk_uuid, inst.name))
367
368 instance.disks.insert(idx, disk_uuid)
369 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
370 _UpdateIvNames(idx, instance_disks[idx:])
371 instance.serial_no += 1
372 instance.mtime = time.time()
373
374 @ConfigSync()
376 """Add a disk to the config and attach it to instance.
377
378 This is a simple wrapper over L{_UnlockedAddDisk} and
379 L{_UnlockedAttachInstanceDisk}.
380
381 """
382 self._UnlockedAddDisk(disk, replace=replace)
383 self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx)
384
385 @ConfigSync()
387 """Attach an existing disk to an instance.
388
389 This is a simple wrapper over L{_UnlockedAttachInstanceDisk}.
390
391 """
392 self._UnlockedAttachInstanceDisk(inst_uuid, disk_uuid, idx)
393
421
423 """Remove the disk from the configuration.
424
425 @type disk_uuid: string
426 @param disk_uuid: The UUID of the disk object
427
428 """
429 if disk_uuid not in self._ConfigData().disks:
430 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)
431
432
433 for inst in self._ConfigData().instances.values():
434 if disk_uuid in inst.disks:
435 raise errors.ReservationError("Cannot remove disk %s. Disk is"
436 " attached to instance %s"
437 % (disk_uuid, inst.name))
438
439
440 del self._ConfigData().disks[disk_uuid]
441 self._ConfigData().cluster.serial_no += 1
442
443 @ConfigSync()
445 """Detach a disk from an instance and remove it from the config.
446
447 This is a simple wrapper over L{_UnlockedDetachInstanceDisk} and
448 L{_UnlockedRemoveDisk}.
449
450 """
451 self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid)
452 self._UnlockedRemoveDisk(disk_uuid)
453
454 @ConfigSync()
456 """Detach a disk from an instance.
457
458 This is a simple wrapper over L{_UnlockedDetachInstanceDisk}.
459 """
460 self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid)
461
463 """Returns information about a disk.
464
465 It takes the information from the configuration file.
466
467 @param disk_uuid: UUID of the disk
468
469 @rtype: L{objects.Disk}
470 @return: the disk object
471
472 """
473 if disk_uuid not in self._ConfigData().disks:
474 return None
475
476 return self._ConfigData().disks[disk_uuid]
477
478 @ConfigSync(shared=1)
480 """Returns information about a disk.
481
482 This is a simple wrapper over L{_UnlockedGetDiskInfo}.
483
484 """
485 return self._UnlockedGetDiskInfo(disk_uuid)
486
488 """Return information about a named disk.
489
490 Return disk information from the configuration file, searching with the
491 name of the disk.
492
493 @param disk_name: Name of the disk
494
495 @rtype: L{objects.Disk}
496 @return: the disk object
497
498 """
499 disk = None
500 count = 0
501 for d in self._ConfigData().disks.itervalues():
502 if d.name == disk_name:
503 count += 1
504 disk = d
505
506 if count > 1:
507 raise errors.ConfigurationError("There are %s disks with this name: %s"
508 % (count, disk_name))
509
510 return disk
511
512 @ConfigSync(shared=1)
514 """Return information about a named disk.
515
516 This is a simple wrapper over L{_UnlockedGetDiskInfoByName}.
517
518 """
519 return self._UnlockedGetDiskInfoByName(disk_name)
520
522 """Get the list of disks.
523
524 @return: array of disks, ex. ['disk2-uuid', 'disk1-uuid']
525
526 """
527 return self._ConfigData().disks.keys()
528
529 @ConfigSync(shared=1)
531 """Get the configuration of all disks.
532
533 This is a simple wrapper over L{_UnlockedGetAllDisksInfo}.
534
535 """
536 return self._UnlockedGetAllDisksInfo()
537
539 """Get the configuration of all disks.
540
541 @rtype: dict
542 @return: dict of (disk, disk_info), where disk_info is what
543 would GetDiskInfo return for the node
544
545 """
546 my_dict = dict([(disk_uuid, self._UnlockedGetDiskInfo(disk_uuid))
547 for disk_uuid in self._UnlockedGetDiskList()])
548 return my_dict
549
551 """Compute the set of all disk-related nodes for an instance.
552
553 This abstracts away some work from '_UnlockedGetInstanceNodes'
554 and '_UnlockedGetInstanceSecondaryNodes'.
555
556 @type inst_uuid: string
557 @param inst_uuid: The UUID of the instance we want to get nodes for
558 @rtype: set of strings
559 @return: A set of names for all the nodes of the instance
560
561 """
562 instance = self._UnlockedGetInstanceInfo(inst_uuid)
563 if instance is None:
564 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
565
566 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
567 all_nodes = []
568 for disk in instance_disks:
569 all_nodes.extend(disk.all_nodes)
570 return (set(all_nodes), instance)
571
573 """Get all disk-related nodes for an instance.
574
575 For non-DRBD instances, this will contain only the instance's primary node,
576 whereas for DRBD instances, it will contain both the primary and the
577 secondaries.
578
579 @type inst_uuid: string
580 @param inst_uuid: The UUID of the instance we want to get nodes for
581 @rtype: list of strings
582 @return: A list of names for all the nodes of the instance
583
584 """
585 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
586
587 all_nodes.discard(instance.primary_node)
588 return (instance.primary_node, ) + tuple(all_nodes)
589
590 @ConfigSync(shared=1)
592 """Get all disk-related nodes for an instance.
593
594 This is just a wrapper over L{_UnlockedGetInstanceNodes}
595
596 """
597 return self._UnlockedGetInstanceNodes(inst_uuid)
598
600 """Get the list of secondary nodes.
601
602 @type inst_uuid: string
603 @param inst_uuid: The UUID of the instance we want to get nodes for
604 @rtype: list of strings
605 @return: A tuple of names for all the secondary nodes of the instance
606
607 """
608 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
609 all_nodes.discard(instance.primary_node)
610 return tuple(all_nodes)
611
612 @ConfigSync(shared=1)
614 """Get the list of secondary nodes.
615
616 This is a simple wrapper over L{_UnlockedGetInstanceSecondaryNodes}.
617
618 """
619 return self._UnlockedGetInstanceSecondaryNodes(inst_uuid)
620
622 """Provide a mapping of node to LVs a given instance owns.
623
624 @type inst_uuid: string
625 @param inst_uuid: The UUID of the instance we want to
626 compute the LVsByNode for
627 @type lvmap: dict
628 @param lvmap: Optional dictionary to receive the
629 'node' : ['lv', ...] data.
630 @rtype: dict or None
631 @return: None if lvmap arg is given, otherwise, a dictionary of
632 the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
633 volumeN is of the form "vg_name/lv_name", compatible with
634 GetVolumeList()
635
636 """
637 def _MapLVsByNode(lvmap, devices, node_uuid):
638 """Recursive helper function."""
639 if not node_uuid in lvmap:
640 lvmap[node_uuid] = []
641
642 for dev in devices:
643 if dev.dev_type == constants.DT_PLAIN:
644 if not dev.forthcoming:
645 lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1])
646
647 elif dev.dev_type in constants.DTS_DRBD:
648 if dev.children:
649 _MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
650 _MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
651
652 elif dev.children:
653 _MapLVsByNode(lvmap, dev.children, node_uuid)
654
655 instance = self._UnlockedGetInstanceInfo(inst_uuid)
656 if instance is None:
657 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
658
659 if lvmap is None:
660 lvmap = {}
661 ret = lvmap
662 else:
663 ret = None
664
665 _MapLVsByNode(lvmap,
666 self._UnlockedGetInstanceDisks(instance.uuid),
667 instance.primary_node)
668 return ret
669
670 @ConfigSync(shared=1)
672 """Provide a mapping of node to LVs a given instance owns.
673
674 This is a simple wrapper over L{_UnlockedGetInstanceLVsByNode}
675
676 """
677 return self._UnlockedGetInstanceLVsByNode(inst_uuid, lvmap=lvmap)
678
679 @ConfigSync(shared=1)
681 """Get the disk params populated with inherit chain.
682
683 @type group: L{objects.NodeGroup}
684 @param group: The group we want to know the params for
685 @return: A dict with the filled in disk params
686
687 """
688 return self._UnlockedGetGroupDiskParams(group)
689
691 """Get the disk params populated with inherit chain down to node-group.
692
693 @type group: L{objects.NodeGroup}
694 @param group: The group we want to know the params for
695 @return: A dict with the filled in disk params
696
697 """
698 data = self._ConfigData().cluster.SimpleFillDP(group.diskparams)
699 assert isinstance(data, dict), "Not a dictionary: " + str(data)
700 return data
701
702 @ConfigSync(shared=1)
704 """Gets the list of node names of potential master candidates.
705
706 @rtype: list of str
707 @return: list of node names of potential master candidates
708
709 """
710
711
712
713 nodes = self._UnlockedGetAllNodesInfo()
714 return [node_info.name for node_info in nodes.values()]
715
717 """Generate a MAC for an instance.
718
719 This should check the current instances for duplicates.
720
721 """
722 return self._wconfd.GenerateMAC(self._GetWConfdContext(), net_uuid)
723
725 """Reserve a MAC for an instance.
726
727 This only checks instances managed by this cluster, it does not
728 check for potential collisions elsewhere.
729
730 """
731 self._wconfd.ReserveMAC(self._GetWConfdContext(), mac)
732
743
758
759 - def ReleaseIp(self, net_uuid, address, _ec_id):
760 """Give a specific IP address back to an IP pool.
761
762 The IP address is returned to the IP pool and marked as reserved.
763
764 """
765 if net_uuid:
766 if self._offline:
767 raise errors.ProgrammerError("Can't call ReleaseIp in offline mode")
768 self._wconfd.ReleaseIp(self._GetWConfdContext(), net_uuid, address)
769
777
778 - def ReserveIp(self, net_uuid, address, _ec_id, check=True):
779 """Reserve a given IPv4 address for use by an instance.
780
781 """
782 if self._offline:
783 raise errors.ProgrammerError("Can't call ReserveIp in offline mode")
784 return self._wconfd.ReserveIp(self._GetWConfdContext(), net_uuid, address,
785 check)
786
788 """Reserve an VG/LV pair for an instance.
789
790 @type lv_name: string
791 @param lv_name: the logical volume name to reserve
792
793 """
794 return self._wconfd.ReserveLV(self._GetWConfdContext(), lv_name)
795
803
804
815
817 """Compute the list of all NICs.
818
819 """
820 nics = []
821 for instance in self._ConfigData().instances.values():
822 nics.extend(instance.nics)
823 return nics
824
825 - def _AllIDs(self, include_temporary):
826 """Compute the list of all UUIDs and names we have.
827
828 @type include_temporary: boolean
829 @param include_temporary: whether to include the _temporary_ids set
830 @rtype: set
831 @return: a set of IDs
832
833 """
834 existing = set()
835 if include_temporary:
836 existing.update(self._temporary_ids.GetReserved())
837 existing.update(self._AllLVs())
838 existing.update(self._ConfigData().instances.keys())
839 existing.update(self._ConfigData().nodes.keys())
840 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
841 return existing
842
844 """Generate an unique UUID.
845
846 This checks the current node, instances and disk names for
847 duplicates.
848
849 @rtype: string
850 @return: the unique id
851
852 """
853 existing = self._AllIDs(include_temporary=False)
854 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
855
856 @ConfigSync(shared=1)
858 """Generate an unique ID.
859
860 This is just a wrapper over the unlocked version.
861
862 @type ec_id: string
863 @param ec_id: unique id for the job to reserve the id to
864
865 """
866 return self._GenerateUniqueID(ec_id)
867
869 """Return all MACs present in the config.
870
871 @rtype: list
872 @return: the list of all MACs
873
874 """
875 result = []
876 for instance in self._ConfigData().instances.values():
877 for nic in instance.nics:
878 result.append(nic.mac)
879
880 return result
881
883 """Return all DRBD secrets present in the config.
884
885 @rtype: list
886 @return: the list of all DRBD secrets
887
888 """
889 def helper(disk, result):
890 """Recursively gather secrets from this disk."""
891 if disk.dev_type == constants.DT_DRBD8:
892 result.append(disk.logical_id[5])
893 if disk.children:
894 for child in disk.children:
895 helper(child, result)
896
897 result = []
898 for disk in self._ConfigData().disks.values():
899 helper(disk, result)
900
901 return result
902
903 @staticmethod
905 """Per-disk verification checks
906
907 Extends L{result} with diagnostic information about the disks.
908
909 @type data: see L{_ConfigData}
910 @param data: configuration data
911
912 @type result: list of strings
913 @param result: list containing diagnostic messages
914
915 """
916 for disk_uuid in data.disks:
917 disk = data.disks[disk_uuid]
918 result.extend(["disk %s error: %s" % (disk.uuid, msg)
919 for msg in disk.Verify()])
920 if disk.uuid != disk_uuid:
921 result.append("disk '%s' is indexed by wrong UUID '%s'" %
922 (disk.name, disk_uuid))
923
925 """Verify function.
926
927 @rtype: list
928 @return: a list of error messages; a non-empty list signifies
929 configuration errors
930
931 """
932
933 result = []
934 seen_macs = []
935 ports = {}
936 data = self._ConfigData()
937 cluster = data.cluster
938
939
940 if not self._offline:
941 try:
942 self._wconfd.VerifyConfig()
943 except errors.ConfigVerifyError, err:
944 try:
945 for msg in err.args[1]:
946 result.append(msg)
947 except IndexError:
948 pass
949
950
951 VerifyType("cluster", "beparams", cluster.SimpleFillBE({}),
952 constants.BES_PARAMETER_TYPES, result.append)
953 VerifyType("cluster", "nicparams", cluster.SimpleFillNIC({}),
954 constants.NICS_PARAMETER_TYPES, result.append)
955 VerifyNic("cluster", cluster.SimpleFillNIC({}), result.append)
956 VerifyType("cluster", "ndparams", cluster.SimpleFillND({}),
957 constants.NDS_PARAMETER_TYPES, result.append)
958 VerifyIpolicy("cluster", cluster.ipolicy, True, result.append)
959
960 for disk_template in cluster.diskparams:
961 if disk_template not in constants.DTS_HAVE_ACCESS:
962 continue
963
964 access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS,
965 constants.DISK_KERNELSPACE)
966 if access not in constants.DISK_VALID_ACCESS_MODES:
967 result.append(
968 "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
969 disk_template, constants.LDP_ACCESS, access,
970 utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
971 )
972 )
973
974 self._VerifyDisks(data, result)
975
976
977 for instance_uuid in data.instances:
978 instance = data.instances[instance_uuid]
979 if instance.uuid != instance_uuid:
980 result.append("instance '%s' is indexed by wrong UUID '%s'" %
981 (instance.name, instance_uuid))
982 if instance.primary_node not in data.nodes:
983 result.append("instance '%s' has invalid primary node '%s'" %
984 (instance.name, instance.primary_node))
985 for snode in self._UnlockedGetInstanceSecondaryNodes(instance.uuid):
986 if snode not in data.nodes:
987 result.append("instance '%s' has invalid secondary node '%s'" %
988 (instance.name, snode))
989 for idx, nic in enumerate(instance.nics):
990 if nic.mac in seen_macs:
991 result.append("instance '%s' has NIC %d mac %s duplicate" %
992 (instance.name, idx, nic.mac))
993 else:
994 seen_macs.append(nic.mac)
995 if nic.nicparams:
996 filled = cluster.SimpleFillNIC(nic.nicparams)
997 owner = "instance %s nic %d" % (instance.name, idx)
998 VerifyType(owner, "nicparams",
999 filled, constants.NICS_PARAMETER_TYPES, result.append)
1000 VerifyNic(owner, filled, result.append)
1001
1002
1003 if instance.beparams:
1004 VerifyType("instance %s" % instance.name, "beparams",
1005 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES,
1006 result.append)
1007
1008
1009 for disk_uuid in instance.disks:
1010 if disk_uuid not in data.disks:
1011 result.append("Instance '%s' has invalid disk '%s'" %
1012 (instance.name, disk_uuid))
1013
1014 instance_disks = self._UnlockedGetInstanceDisks(instance.uuid)
1015
1016 for (idx, dsk) in enumerate(instance_disks):
1017 if dsk.dev_type in constants.DTS_DRBD:
1018 tcp_port = dsk.logical_id[2]
1019 if tcp_port not in ports:
1020 ports[tcp_port] = []
1021 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
1022
1023 net_port = getattr(instance, "network_port", None)
1024 if net_port is not None:
1025 if net_port not in ports:
1026 ports[net_port] = []
1027 ports[net_port].append((instance.name, "network port"))
1028
1029 wrong_names = _CheckInstanceDiskIvNames(instance_disks)
1030 if wrong_names:
1031 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
1032 (idx, exp_name, actual_name))
1033 for (idx, exp_name, actual_name) in wrong_names)
1034
1035 result.append("Instance '%s' has wrongly named disks: %s" %
1036 (instance.name, tmp))
1037
1038
1039 for free_port in cluster.tcpudp_port_pool:
1040 if free_port not in ports:
1041 ports[free_port] = []
1042 ports[free_port].append(("cluster", "port marked as free"))
1043
1044
1045 keys = ports.keys()
1046 keys.sort()
1047 for pnum in keys:
1048 pdata = ports[pnum]
1049 if len(pdata) > 1:
1050 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
1051 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
1052
1053
1054 if keys:
1055 if keys[-1] > cluster.highest_used_port:
1056 result.append("Highest used port mismatch, saved %s, computed %s" %
1057 (cluster.highest_used_port, keys[-1]))
1058
1059 if not data.nodes[cluster.master_node].master_candidate:
1060 result.append("Master node is not a master candidate")
1061
1062
1063 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
1064 if mc_now < mc_max:
1065 result.append("Not enough master candidates: actual %d, target %d" %
1066 (mc_now, mc_max))
1067
1068
1069 for node_uuid, node in data.nodes.items():
1070 if node.uuid != node_uuid:
1071 result.append("Node '%s' is indexed by wrong UUID '%s'" %
1072 (node.name, node_uuid))
1073 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
1074 result.append("Node %s state is invalid: master_candidate=%s,"
1075 " drain=%s, offline=%s" %
1076 (node.name, node.master_candidate, node.drained,
1077 node.offline))
1078 if node.group not in data.nodegroups:
1079 result.append("Node '%s' has invalid group '%s'" %
1080 (node.name, node.group))
1081 else:
1082 VerifyType("node %s" % node.name, "ndparams",
1083 cluster.FillND(node, data.nodegroups[node.group]),
1084 constants.NDS_PARAMETER_TYPES, result.append)
1085 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
1086 if used_globals:
1087 result.append("Node '%s' has some global parameters set: %s" %
1088 (node.name, utils.CommaJoin(used_globals)))
1089
1090
1091 nodegroups_names = set()
1092 for nodegroup_uuid in data.nodegroups:
1093 nodegroup = data.nodegroups[nodegroup_uuid]
1094 if nodegroup.uuid != nodegroup_uuid:
1095 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
1096 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
1097 if utils.UUID_RE.match(nodegroup.name.lower()):
1098 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
1099 (nodegroup.name, nodegroup.uuid))
1100 if nodegroup.name in nodegroups_names:
1101 result.append("duplicate node group name '%s'" % nodegroup.name)
1102 else:
1103 nodegroups_names.add(nodegroup.name)
1104 group_name = "group %s" % nodegroup.name
1105 VerifyIpolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
1106 False, result.append)
1107 if nodegroup.ndparams:
1108 VerifyType(group_name, "ndparams",
1109 cluster.SimpleFillND(nodegroup.ndparams),
1110 constants.NDS_PARAMETER_TYPES, result.append)
1111
1112
1113
1114
1115
1116 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
1117 ips = {}
1118
1119 def _AddIpAddress(ip, name):
1120 ips.setdefault(ip, []).append(name)
1121
1122 _AddIpAddress(cluster.master_ip, "cluster_ip")
1123
1124 for node in data.nodes.values():
1125 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
1126 if node.secondary_ip != node.primary_ip:
1127 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
1128
1129 for instance in data.instances.values():
1130 for idx, nic in enumerate(instance.nics):
1131 if nic.ip is None:
1132 continue
1133
1134 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
1135 nic_mode = nicparams[constants.NIC_MODE]
1136 nic_link = nicparams[constants.NIC_LINK]
1137
1138 if nic_mode == constants.NIC_MODE_BRIDGED:
1139 link = "bridge:%s" % nic_link
1140 elif nic_mode == constants.NIC_MODE_ROUTED:
1141 link = "route:%s" % nic_link
1142 elif nic_mode == constants.NIC_MODE_OVS:
1143 link = "ovs:%s" % nic_link
1144 else:
1145 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
1146
1147 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
1148 "instance:%s/nic:%d" % (instance.name, idx))
1149
1150 for ip, owners in ips.items():
1151 if len(owners) > 1:
1152 result.append("IP address %s is used by multiple owners: %s" %
1153 (ip, utils.CommaJoin(owners)))
1154
1155 return result
1156
1158 """Verify the configuration and log any errors.
1159
1160 The errors get logged as critical errors and also to the feedback function,
1161 if given.
1162
1163 @param feedback_fn: Callable feedback function
1164 @rtype: list
1165 @return: a list of error messages; a non-empty list signifies
1166 configuration errors
1167
1168 """
1169 assert feedback_fn is None or callable(feedback_fn)
1170
1171
1172
1173
1174
1175 config_errors = self._UnlockedVerifyConfig()
1176 if config_errors:
1177 errmsg = ("Configuration data is not consistent: %s" %
1178 (utils.CommaJoin(config_errors)))
1179 logging.critical(errmsg)
1180 if feedback_fn:
1181 feedback_fn(errmsg)
1182 return config_errors
1183
1184 @ConfigSync(shared=1)
1186 """Verify function.
1187
1188 This is just a wrapper over L{_UnlockedVerifyConfig}.
1189
1190 @rtype: list
1191 @return: a list of error messages; a non-empty list signifies
1192 configuration errors
1193
1194 """
1195 return self._UnlockedVerifyConfig()
1196
1197 @ConfigSync()
1199 """Adds a new port to the available port pool.
1200
1201 @warning: this method does not "flush" the configuration (via
1202 L{_WriteConfig}); callers should do that themselves once the
1203 configuration is stable
1204
1205 """
1206 if not isinstance(port, int):
1207 raise errors.ProgrammerError("Invalid type passed for port")
1208
1209 self._ConfigData().cluster.tcpudp_port_pool.add(port)
1210
1211 @ConfigSync(shared=1)
1213 """Returns a copy of the current port list.
1214
1215 """
1216 return self._ConfigData().cluster.tcpudp_port_pool.copy()
1217
1218 @ConfigSync()
1238
1239 @ConfigSync(shared=1)
1241 """Compute the used DRBD minor/nodes.
1242
1243 This is just a wrapper over a call to WConfd.
1244
1245 @return: dictionary of node_uuid: dict of minor: instance_uuid;
1246 the returned dict will have all the nodes in it (even if with
1247 an empty list).
1248
1249 """
1250 if self._offline:
1251 raise errors.ProgrammerError("Can't call ComputeDRBDMap in offline mode")
1252 else:
1253 return dict(map(lambda (k, v): (k, dict(v)),
1254 self._wconfd.ComputeDRBDMap()))
1255
1257 """Allocate a drbd minor.
1258
1259 This is just a wrapper over a call to WConfd.
1260
1261 The free minor will be automatically computed from the existing
1262 devices. A node can not be given multiple times.
1263 The result is the list of minors, in the same
1264 order as the passed nodes.
1265
1266 @type node_uuids: list of strings
1267 @param node_uuids: the nodes in which we allocate minors
1268 @type disk_uuid: string
1269 @param disk_uuid: the disk for which we allocate minors
1270 @rtype: list of ints
1271 @return: A list of minors in the same order as the passed nodes
1272
1273 """
1274 assert isinstance(disk_uuid, basestring), \
1275 "Invalid argument '%s' passed to AllocateDRBDMinor" % disk_uuid
1276
1277 if self._offline:
1278 raise errors.ProgrammerError("Can't call AllocateDRBDMinor"
1279 " in offline mode")
1280
1281 result = self._wconfd.AllocateDRBDMinor(disk_uuid, node_uuids)
1282 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1283 node_uuids, result)
1284 return result
1285
1287 """Release temporary drbd minors allocated for a given disk.
1288
1289 This is just a wrapper over a call to WConfd.
1290
1291 @type disk_uuid: string
1292 @param disk_uuid: the disk for which temporary minors should be released
1293
1294 """
1295 assert isinstance(disk_uuid, basestring), \
1296 "Invalid argument passed to ReleaseDRBDMinors"
1297
1298
1299
1300 if not self._offline:
1301 self._wconfd.ReleaseDRBDMinors(disk_uuid)
1302
1303 @ConfigSync()
1305 """Release temporary drbd minors allocated for a given disk.
1306
1307 This should be called on the error paths, on the success paths
1308 it's automatically called by the ConfigWriter add and update
1309 functions.
1310
1311 This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
1312
1313 @type disk_uuid: string
1314 @param disk_uuid: the disk for which temporary minors should be released
1315
1316 """
1317 self._UnlockedReleaseDRBDMinors(disk_uuid)
1318
1319 @ConfigSync(shared=1)
1321 """Return the disk template of an instance.
1322
1323 This corresponds to the currently attached disks. If no disks are attached,
1324 it is L{constants.DT_DISKLESS}, if homogeneous disk types are attached,
1325 that type is returned, if that isn't the case, L{constants.DT_MIXED} is
1326 returned.
1327
1328 @type inst_uuid: str
1329 @param inst_uuid: The uuid of the instance.
1330 """
1331 return utils.GetDiskTemplate(self._UnlockedGetInstanceDisks(inst_uuid))
1332
1333 @ConfigSync(shared=1)
1335 """Get the configuration version.
1336
1337 @return: Config version
1338
1339 """
1340 return self._ConfigData().version
1341
1342 @ConfigSync(shared=1)
1344 """Get cluster name.
1345
1346 @return: Cluster name
1347
1348 """
1349 return self._ConfigData().cluster.cluster_name
1350
1351 @ConfigSync(shared=1)
1353 """Get the UUID of the master node for this cluster.
1354
1355 @return: Master node UUID
1356
1357 """
1358 return self._ConfigData().cluster.master_node
1359
1360 @ConfigSync(shared=1)
1362 """Get the hostname of the master node for this cluster.
1363
1364 @return: Master node hostname
1365
1366 """
1367 return self._UnlockedGetNodeName(self._ConfigData().cluster.master_node)
1368
1369 @ConfigSync(shared=1)
1371 """Get the master node information for this cluster.
1372
1373 @rtype: objects.Node
1374 @return: Master node L{objects.Node} object
1375
1376 """
1377 return self._UnlockedGetNodeInfo(self._ConfigData().cluster.master_node)
1378
1379 @ConfigSync(shared=1)
1381 """Get the IP of the master node for this cluster.
1382
1383 @return: Master IP
1384
1385 """
1386 return self._ConfigData().cluster.master_ip
1387
1388 @ConfigSync(shared=1)
1390 """Get the master network device for this cluster.
1391
1392 """
1393 return self._ConfigData().cluster.master_netdev
1394
1395 @ConfigSync(shared=1)
1397 """Get the netmask of the master node for this cluster.
1398
1399 """
1400 return self._ConfigData().cluster.master_netmask
1401
1402 @ConfigSync(shared=1)
1404 """Get flag representing whether to use the external master IP setup script.
1405
1406 """
1407 return self._ConfigData().cluster.use_external_mip_script
1408
1409 @ConfigSync(shared=1)
1411 """Get the file storage dir for this cluster.
1412
1413 """
1414 return self._ConfigData().cluster.file_storage_dir
1415
1416 @ConfigSync(shared=1)
1418 """Get the shared file storage dir for this cluster.
1419
1420 """
1421 return self._ConfigData().cluster.shared_file_storage_dir
1422
1423 @ConfigSync(shared=1)
1425 """Get the Gluster storage dir for this cluster.
1426
1427 """
1428 return self._ConfigData().cluster.gluster_storage_dir
1429
1430 @ConfigSync(shared=1)
1432 """Get the hypervisor type for this cluster.
1433
1434 """
1435 return self._ConfigData().cluster.enabled_hypervisors[0]
1436
1437 @ConfigSync(shared=1)
1439 """Return the rsa hostkey from the config.
1440
1441 @rtype: string
1442 @return: the rsa hostkey
1443
1444 """
1445 return self._ConfigData().cluster.rsahostkeypub
1446
1447 @ConfigSync(shared=1)
1449 """Return the dsa hostkey from the config.
1450
1451 @rtype: string
1452 @return: the dsa hostkey
1453
1454 """
1455 return self._ConfigData().cluster.dsahostkeypub
1456
1457 @ConfigSync(shared=1)
1459 """Get the default instance allocator for this cluster.
1460
1461 """
1462 return self._ConfigData().cluster.default_iallocator
1463
1464 @ConfigSync(shared=1)
1466 """Get the default instance allocator parameters for this cluster.
1467
1468 @rtype: dict
1469 @return: dict of iallocator parameters
1470
1471 """
1472 return self._ConfigData().cluster.default_iallocator_params
1473
1474 @ConfigSync(shared=1)
1476 """Get cluster primary ip family.
1477
1478 @return: primary ip family
1479
1480 """
1481 return self._ConfigData().cluster.primary_ip_family
1482
1483 @ConfigSync(shared=1)
1498
1499 @ConfigSync(shared=1)
1501 """Get the install image location
1502
1503 @rtype: string
1504 @return: location of the install image
1505
1506 """
1507 return self._ConfigData().cluster.install_image
1508
1509 @ConfigSync()
1511 """Set the install image location
1512
1513 @type install_image: string
1514 @param install_image: location of the install image
1515
1516 """
1517 self._ConfigData().cluster.install_image = install_image
1518
1519 @ConfigSync(shared=1)
1521 """Get cluster instance communication network
1522
1523 @rtype: string
1524 @return: instance communication network, which is the name of the
1525 network used for instance communication
1526
1527 """
1528 return self._ConfigData().cluster.instance_communication_network
1529
1530 @ConfigSync()
1532 """Set cluster instance communication network
1533
1534 @type network_name: string
1535 @param network_name: instance communication network, which is the name of
1536 the network used for instance communication
1537
1538 """
1539 self._ConfigData().cluster.instance_communication_network = network_name
1540
1541 @ConfigSync(shared=1)
1543 """Get the zeroing image location
1544
1545 @rtype: string
1546 @return: the location of the zeroing image
1547
1548 """
1549 return self._config_data.cluster.zeroing_image
1550
1551 @ConfigSync(shared=1)
1561
1562 @ConfigSync()
1572
1573 @ConfigSync()
1575 """Add a node group to the configuration.
1576
1577 This method calls group.UpgradeConfig() to fill any missing attributes
1578 according to their default values.
1579
1580 @type group: L{objects.NodeGroup}
1581 @param group: the NodeGroup object to add
1582 @type ec_id: string
1583 @param ec_id: unique id for the job to use when creating a missing UUID
1584 @type check_uuid: bool
1585 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1586 it does, ensure that it does not exist in the
1587 configuration already
1588
1589 """
1590 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1591
1620
1621 @ConfigSync()
1623 """Remove a node group from the configuration.
1624
1625 @type group_uuid: string
1626 @param group_uuid: the UUID of the node group to remove
1627
1628 """
1629 logging.info("Removing node group %s from configuration", group_uuid)
1630
1631 if group_uuid not in self._ConfigData().nodegroups:
1632 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1633
1634 assert len(self._ConfigData().nodegroups) != 1, \
1635 "Group '%s' is the only group, cannot be removed" % group_uuid
1636
1637 del self._ConfigData().nodegroups[group_uuid]
1638 self._ConfigData().cluster.serial_no += 1
1639
1641 """Lookup a node group's UUID.
1642
1643 @type target: string or None
1644 @param target: group name or UUID or None to look for the default
1645 @rtype: string
1646 @return: nodegroup UUID
1647 @raises errors.OpPrereqError: when the target group cannot be found
1648
1649 """
1650 if target is None:
1651 if len(self._ConfigData().nodegroups) != 1:
1652 raise errors.OpPrereqError("More than one node group exists. Target"
1653 " group must be specified explicitly.")
1654 else:
1655 return self._ConfigData().nodegroups.keys()[0]
1656 if target in self._ConfigData().nodegroups:
1657 return target
1658 for nodegroup in self._ConfigData().nodegroups.values():
1659 if nodegroup.name == target:
1660 return nodegroup.uuid
1661 raise errors.OpPrereqError("Node group '%s' not found" % target,
1662 errors.ECODE_NOENT)
1663
1664 @ConfigSync(shared=1)
1666 """Lookup a node group's UUID.
1667
1668 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1669
1670 @type target: string or None
1671 @param target: group name or UUID or None to look for the default
1672 @rtype: string
1673 @return: nodegroup UUID
1674
1675 """
1676 return self._UnlockedLookupNodeGroup(target)
1677
1679 """Lookup a node group.
1680
1681 @type uuid: string
1682 @param uuid: group UUID
1683 @rtype: L{objects.NodeGroup} or None
1684 @return: nodegroup object, or None if not found
1685
1686 """
1687 if uuid not in self._ConfigData().nodegroups:
1688 return None
1689
1690 return self._ConfigData().nodegroups[uuid]
1691
1692 @ConfigSync(shared=1)
1694 """Lookup a node group.
1695
1696 @type uuid: string
1697 @param uuid: group UUID
1698 @rtype: L{objects.NodeGroup} or None
1699 @return: nodegroup object, or None if not found
1700
1701 """
1702 return self._UnlockedGetNodeGroup(uuid)
1703
1705 """Get the configuration of all node groups.
1706
1707 """
1708 return dict(self._ConfigData().nodegroups)
1709
1710 @ConfigSync(shared=1)
1716
1717 @ConfigSync(shared=1)
1719 """Get the configuration of all node groups expressed as a dictionary of
1720 dictionaries.
1721
1722 """
1723 return dict(map(lambda (uuid, ng): (uuid, ng.ToDict()),
1724 self._UnlockedGetAllNodeGroupsInfo().items()))
1725
1726 @ConfigSync(shared=1)
1728 """Get a list of node groups.
1729
1730 """
1731 return self._ConfigData().nodegroups.keys()
1732
1733 @ConfigSync(shared=1)
1735 """Get nodes which are member in the same nodegroups as the given nodes.
1736
1737 """
1738 ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1739 return frozenset(member_uuid
1740 for node_uuid in nodes
1741 for member_uuid in
1742 self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1743
1744 @ConfigSync(shared=1)
1746 """Get the configuration of multiple node groups.
1747
1748 @param group_uuids: List of node group UUIDs
1749 @rtype: list
1750 @return: List of tuples of (group_uuid, group_info)
1751
1752 """
1753 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1754
1755 - def AddInstance(self, instance, _ec_id, replace=False):
1788
1790 """Ensures a given object has a valid UUID.
1791
1792 @param item: the instance or node to be checked
1793 @param ec_id: the execution context id for the uuid reservation
1794
1795 """
1796 if not item.uuid:
1797 item.uuid = self._GenerateUniqueID(ec_id)
1798 else:
1799 self._CheckUniqueUUID(item, include_temporary=True)
1800
1802 """Checks that the UUID of the given object is unique.
1803
1804 @param item: the instance or node to be checked
1805 @param include_temporary: whether temporarily generated UUID's should be
1806 included in the check. If the UUID of the item to be checked is
1807 a temporarily generated one, this has to be C{False}.
1808
1809 """
1810 if not item.uuid:
1811 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1812 if item.uuid in self._AllIDs(include_temporary=include_temporary):
1813 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1814 " in use" % (item.name, item.uuid))
1815
1817 """Checks that an object with the given UUID exists.
1818
1819 @param item: the instance or other UUID possessing object to verify that
1820 its UUID is present
1821
1822 """
1823 if not item.uuid:
1824 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1825 if item.uuid not in self._AllIDs(include_temporary=False):
1826 raise errors.ConfigurationError("Cannot replace '%s': UUID %s not present"
1827 % (item.name, item.uuid))
1828
1831 """Set the instance's status to a given value.
1832
1833 @rtype: L{objects.Instance}
1834 @return: the updated instance object
1835
1836 """
1837 if inst_uuid not in self._ConfigData().instances:
1838 raise errors.ConfigurationError("Unknown instance '%s'" %
1839 inst_uuid)
1840 instance = self._ConfigData().instances[inst_uuid]
1841
1842 if status is None:
1843 status = instance.admin_state
1844 if disks_active is None:
1845 disks_active = instance.disks_active
1846 if admin_state_source is None:
1847 admin_state_source = instance.admin_state_source
1848
1849 assert status in constants.ADMINST_ALL, \
1850 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
1851
1852 if instance.admin_state != status or \
1853 instance.disks_active != disks_active or \
1854 instance.admin_state_source != admin_state_source:
1855 instance.admin_state = status
1856 instance.disks_active = disks_active
1857 instance.admin_state_source = admin_state_source
1858 instance.serial_no += 1
1859 instance.mtime = time.time()
1860 return instance
1861
1862 @ConfigSync()
1864 """Mark the instance status to up in the config.
1865
1866 This also sets the instance disks active flag.
1867
1868 @rtype: L{objects.Instance}
1869 @return: the updated instance object
1870
1871 """
1872 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True,
1873 constants.ADMIN_SOURCE)
1874
1875 @ConfigSync()
1877 """Mark the instance status to down in the config.
1878
1879 This also clears the instance disks active flag.
1880
1881 @rtype: L{objects.Instance}
1882 @return: the updated instance object
1883
1884 """
1885 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False,
1886 constants.ADMIN_SOURCE)
1887
1888 @ConfigSync()
1914
1915 @ConfigSync()
1917 """Rename an instance.
1918
1919 This needs to be done in ConfigWriter and not by RemoveInstance
1920 combined with AddInstance as only we can guarantee an atomic
1921 rename.
1922
1923 """
1924 if inst_uuid not in self._ConfigData().instances:
1925 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1926
1927 inst = self._ConfigData().instances[inst_uuid]
1928 inst.name = new_name
1929
1930 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
1931 for (_, disk) in enumerate(instance_disks):
1932 if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1933
1934 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1935 disk.logical_id = (disk.logical_id[0],
1936 utils.PathJoin(file_storage_dir, inst.name,
1937 os.path.basename(disk.logical_id[1])))
1938
1939
1940 self._ConfigData().cluster.serial_no += 1
1941
1942 @ConfigSync()
1944 """Mark the status of an instance to down in the configuration.
1945
1946 This does not touch the instance disks active flag, as shut down instances
1947 can still have active disks.
1948
1949 @rtype: L{objects.Instance}
1950 @return: the updated instance object
1951
1952 """
1953 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None,
1954 constants.ADMIN_SOURCE)
1955
1956 @ConfigSync()
1958 """Mark the status of an instance to user down in the configuration.
1959
1960 This does not touch the instance disks active flag, as user shut
1961 down instances can still have active disks.
1962
1963 """
1964
1965 self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None,
1966 constants.USER_SOURCE)
1967
1968 @ConfigSync()
1970 """Mark the status of instance disks active.
1971
1972 @rtype: L{objects.Instance}
1973 @return: the updated instance object
1974
1975 """
1976 return self._SetInstanceStatus(inst_uuid, None, True, None)
1977
1978 @ConfigSync()
1980 """Mark the status of instance disks inactive.
1981
1982 @rtype: L{objects.Instance}
1983 @return: the updated instance object
1984
1985 """
1986 return self._SetInstanceStatus(inst_uuid, None, False, None)
1987
1989 """Get the list of instances.
1990
1991 This function is for internal use, when the config lock is already held.
1992
1993 """
1994 return self._ConfigData().instances.keys()
1995
1996 @ConfigSync(shared=1)
1998 """Get the list of instances.
1999
2000 @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
2001
2002 """
2003 return self._UnlockedGetInstanceList()
2004
2006 """Attempt to expand an incomplete instance name.
2007
2008 """
2009
2010 all_insts = self.GetAllInstancesInfo().values()
2011 expanded_name = _MatchNameComponentIgnoreCase(
2012 short_name, [inst.name for inst in all_insts])
2013
2014 if expanded_name is not None:
2015
2016 inst = (filter(lambda n: n.name == expanded_name, all_insts)[0])
2017 return (inst.uuid, inst.name)
2018 else:
2019 return (None, None)
2020
2022 """Returns information about an instance.
2023
2024 This function is for internal use, when the config lock is already held.
2025
2026 """
2027 if inst_uuid not in self._ConfigData().instances:
2028 return None
2029
2030 return self._ConfigData().instances[inst_uuid]
2031
2032 @ConfigSync(shared=1)
2034 """Returns information about an instance.
2035
2036 It takes the information from the configuration file. Other information of
2037 an instance are taken from the live systems.
2038
2039 @param inst_uuid: UUID of the instance
2040
2041 @rtype: L{objects.Instance}
2042 @return: the instance object
2043
2044 """
2045 return self._UnlockedGetInstanceInfo(inst_uuid)
2046
2047 @ConfigSync(shared=1)
2065
2066 @ConfigSync(shared=1)
2083
2084 @ConfigSync(shared=1)
2086 """Get the configuration of multiple instances.
2087
2088 @param inst_uuids: list of instance UUIDs
2089 @rtype: list
2090 @return: list of tuples (instance UUID, instance_info), where
2091 instance_info is what would GetInstanceInfo return for the
2092 node, while keeping the original order
2093
2094 """
2095 return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
2096
2097 @ConfigSync(shared=1)
2099 """Get the configuration of multiple instances.
2100
2101 @param inst_names: list of instance names
2102 @rtype: list
2103 @return: list of tuples (instance, instance_info), where
2104 instance_info is what would GetInstanceInfo return for the
2105 node, while keeping the original order
2106
2107 """
2108 result = []
2109 for name in inst_names:
2110 instance = self._UnlockedGetInstanceInfoByName(name)
2111 if instance:
2112 result.append((instance.uuid, instance))
2113 else:
2114 raise errors.ConfigurationError("Instance data of instance '%s'"
2115 " not found." % name)
2116 return result
2117
2118 @ConfigSync(shared=1)
2120 """Get the configuration of all instances.
2121
2122 @rtype: dict
2123 @return: dict of (instance, instance_info), where instance_info is what
2124 would GetInstanceInfo return for the node
2125
2126 """
2127 return self._UnlockedGetAllInstancesInfo()
2128
2133
2134 @ConfigSync(shared=1)
2136 """Get instance configuration with a filter.
2137
2138 @type filter_fn: callable
2139 @param filter_fn: Filter function receiving instance object as parameter,
2140 returning boolean. Important: this function is called while the
2141 configuration locks is held. It must not do any complex work or call
2142 functions potentially leading to a deadlock. Ideally it doesn't call any
2143 other functions and just compares instance attributes.
2144
2145 """
2146 return dict((uuid, inst)
2147 for (uuid, inst) in self._ConfigData().instances.items()
2148 if filter_fn(inst))
2149
2150 @ConfigSync(shared=1)
2152 """Get the L{objects.Instance} object for a named instance.
2153
2154 @param inst_name: name of the instance to get information for
2155 @type inst_name: string
2156 @return: the corresponding L{objects.Instance} instance or None if no
2157 information is available
2158
2159 """
2160 return self._UnlockedGetInstanceInfoByName(inst_name)
2161
2167
2173
2174 @ConfigSync(shared=1)
2176 """Gets the instance name for the passed instance.
2177
2178 @param inst_uuid: instance UUID to get name for
2179 @type inst_uuid: string
2180 @rtype: string
2181 @return: instance name
2182
2183 """
2184 return self._UnlockedGetInstanceName(inst_uuid)
2185
2186 @ConfigSync(shared=1)
2188 """Gets the instance names for the passed list of nodes.
2189
2190 @param inst_uuids: list of instance UUIDs to get names for
2191 @type inst_uuids: list of strings
2192 @rtype: list of strings
2193 @return: list of instance names
2194
2195 """
2196 return self._UnlockedGetInstanceNames(inst_uuids)
2197
2198 @ConfigSync()
2200 """Sets the primary node of an existing instance
2201
2202 @param inst_uuid: instance UUID
2203 @type inst_uuid: string
2204 @param target_node_uuid: the new primary node UUID
2205 @type target_node_uuid: string
2206
2207 """
2208 self._UnlockedGetInstanceInfo(inst_uuid).primary_node = target_node_uuid
2209
2210 @ConfigSync()
2212 """Sets the nodes of an existing disk
2213
2214 @param disk_uuid: disk UUID
2215 @type disk_uuid: string
2216 @param nodes: the new nodes for the disk
2217 @type nodes: list of node uuids
2218
2219 """
2220 self._UnlockedGetDiskInfo(disk_uuid).nodes = nodes
2221
2222 @ConfigSync()
2224 """Sets the logical_id of an existing disk
2225
2226 @param disk_uuid: disk UUID
2227 @type disk_uuid: string
2228 @param logical_id: the new logical_id for the disk
2229 @type logical_id: tuple
2230
2231 """
2232 disk = self._UnlockedGetDiskInfo(disk_uuid)
2233 if disk is None:
2234 raise errors.ConfigurationError("Unknown disk UUID '%s'" % disk_uuid)
2235
2236 if len(disk.logical_id) != len(logical_id):
2237 raise errors.ProgrammerError("Logical ID format mismatch\n"
2238 "Existing logical ID: %s\n"
2239 "New logical ID: %s", disk.logical_id,
2240 logical_id)
2241
2242 disk.logical_id = logical_id
2243
2246
2264
2265 @ConfigSync()
2267 """Add a node to the configuration.
2268
2269 @type node: L{objects.Node}
2270 @param node: a Node instance
2271
2272 """
2273 self._UnlockedAddNode(node, ec_id)
2274
2275 @ConfigSync()
2288
2304
2306 """Get the configuration of a node, as stored in the config.
2307
2308 This function is for internal use, when the config lock is already
2309 held.
2310
2311 @param node_uuid: the node UUID
2312
2313 @rtype: L{objects.Node}
2314 @return: the node object
2315
2316 """
2317 if node_uuid not in self._ConfigData().nodes:
2318 return None
2319
2320 return self._ConfigData().nodes[node_uuid]
2321
2322 @ConfigSync(shared=1)
2324 """Get the configuration of a node, as stored in the config.
2325
2326 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
2327
2328 @param node_uuid: the node UUID
2329
2330 @rtype: L{objects.Node}
2331 @return: the node object
2332
2333 """
2334 return self._UnlockedGetNodeInfo(node_uuid)
2335
2336 @ConfigSync(shared=1)
2338 """Get the instances of a node, as stored in the config.
2339
2340 @param node_uuid: the node UUID
2341
2342 @rtype: (list, list)
2343 @return: a tuple with two lists: the primary and the secondary instances
2344
2345 """
2346 pri = []
2347 sec = []
2348 for inst in self._ConfigData().instances.values():
2349 if inst.primary_node == node_uuid:
2350 pri.append(inst.uuid)
2351 if node_uuid in self._UnlockedGetInstanceSecondaryNodes(inst.uuid):
2352 sec.append(inst.uuid)
2353 return (pri, sec)
2354
2355 @ConfigSync(shared=1)
2357 """Get the instances of a node group.
2358
2359 @param uuid: Node group UUID
2360 @param primary_only: Whether to only consider primary nodes
2361 @rtype: frozenset
2362 @return: List of instance UUIDs in node group
2363
2364 """
2365 if primary_only:
2366 nodes_fn = lambda inst: [inst.primary_node]
2367 else:
2368 nodes_fn = lambda inst: self._UnlockedGetInstanceNodes(inst.uuid)
2369
2370 return frozenset(inst.uuid
2371 for inst in self._ConfigData().instances.values()
2372 for node_uuid in nodes_fn(inst)
2373 if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
2374
2376 """Return the string representation of the list of hyervisor parameters of
2377 the given hypervisor.
2378
2379 @see: C{GetHvparams}
2380
2381 """
2382 result = ""
2383 hvparams = self._ConfigData().cluster.hvparams[hvname]
2384 for key in hvparams:
2385 result += "%s=%s\n" % (key, hvparams[key])
2386 return result
2387
2388 @ConfigSync(shared=1)
2390 """Return the hypervisor parameters of the given hypervisor.
2391
2392 @type hvname: string
2393 @param hvname: name of a hypervisor
2394 @rtype: string
2395 @return: string containing key-value-pairs, one pair on each line;
2396 format: KEY=VALUE
2397
2398 """
2399 return self._UnlockedGetHvparamsString(hvname)
2400
2402 """Return the list of nodes which are in the configuration.
2403
2404 This function is for internal use, when the config lock is already
2405 held.
2406
2407 @rtype: list
2408
2409 """
2410 return self._ConfigData().nodes.keys()
2411
2412 @ConfigSync(shared=1)
2414 """Return the list of nodes which are in the configuration.
2415
2416 """
2417 return self._UnlockedGetNodeList()
2418
2426
2427 @ConfigSync(shared=1)
2433
2434 @ConfigSync(shared=1)
2442
2443 @ConfigSync(shared=1)
2451
2452 @ConfigSync(shared=1)
2460
2461 @ConfigSync(shared=1)
2463 """Get the configuration of multiple nodes.
2464
2465 @param node_uuids: list of node UUIDs
2466 @rtype: list
2467 @return: list of tuples of (node, node_info), where node_info is
2468 what would GetNodeInfo return for the node, in the original
2469 order
2470
2471 """
2472 return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2473
2475 """Gets configuration of all nodes.
2476
2477 @note: See L{GetAllNodesInfo}
2478
2479 """
2480 return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2481 for node_uuid in self._UnlockedGetNodeList()])
2482
2483 @ConfigSync(shared=1)
2485 """Get the configuration of all nodes.
2486
2487 @rtype: dict
2488 @return: dict of (node, node_info), where node_info is what
2489 would GetNodeInfo return for the node
2490
2491 """
2492 return self._UnlockedGetAllNodesInfo()
2493
2499
2500 @ConfigSync(shared=1)
2502 """Get the L{objects.Node} object for a named node.
2503
2504 @param node_name: name of the node to get information for
2505 @type node_name: string
2506 @return: the corresponding L{objects.Node} instance or None if no
2507 information is available
2508
2509 """
2510 return self._UnlockedGetNodeInfoByName(node_name)
2511
2512 @ConfigSync(shared=1)
2514 """Get the L{objects.NodeGroup} object for a named node group.
2515
2516 @param nodegroup_name: name of the node group to get information for
2517 @type nodegroup_name: string
2518 @return: the corresponding L{objects.NodeGroup} instance or None if no
2519 information is available
2520
2521 """
2522 for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2523 if nodegroup.name == nodegroup_name:
2524 return nodegroup
2525 return None
2526
2537
2538 @ConfigSync(shared=1)
2540 """Gets the node name for the passed node.
2541
2542 @param node_spec: node to get names for
2543 @type node_spec: either node UUID or a L{objects.Node} object
2544 @rtype: string
2545 @return: node name
2546
2547 """
2548 return self._UnlockedGetNodeName(node_spec)
2549
2552
2553 @ConfigSync(shared=1)
2555 """Gets the node names for the passed list of nodes.
2556
2557 @param node_specs: list of nodes to get names for
2558 @type node_specs: list of either node UUIDs or L{objects.Node} objects
2559 @rtype: list of strings
2560 @return: list of node names
2561
2562 """
2563 return self._UnlockedGetNodeNames(node_specs)
2564
2565 @ConfigSync(shared=1)
2567 """Returns groups for a list of nodes.
2568
2569 @type node_uuids: list of string
2570 @param node_uuids: List of node UUIDs
2571 @rtype: frozenset
2572
2573 """
2574 return frozenset(self._UnlockedGetNodeInfo(uuid).group
2575 for uuid in node_uuids)
2576
2578 """Get the list of UUIDs of master candidates.
2579
2580 @rtype: list of strings
2581 @return: list of UUIDs of all master candidates.
2582
2583 """
2584 return [node.uuid for node in self._ConfigData().nodes.values()
2585 if node.master_candidate]
2586
2587 @ConfigSync(shared=1)
2589 """Get the list of UUIDs of master candidates.
2590
2591 @rtype: list of strings
2592 @return: list of UUIDs of all master candidates.
2593
2594 """
2595 return self._UnlockedGetMasterCandidateUuids()
2596
2598 """Get the number of current and maximum desired and possible candidates.
2599
2600 @type exceptions: list
2601 @param exceptions: if passed, list of nodes that should be ignored
2602 @rtype: tuple
2603 @return: tuple of (current, desired and possible, possible)
2604
2605 """
2606 mc_now = mc_should = mc_max = 0
2607 for node in self._ConfigData().nodes.values():
2608 if exceptions and node.uuid in exceptions:
2609 continue
2610 if not (node.offline or node.drained) and node.master_capable:
2611 mc_max += 1
2612 if node.master_candidate:
2613 mc_now += 1
2614 mc_should = min(mc_max, self._ConfigData().cluster.candidate_pool_size)
2615 return (mc_now, mc_should, mc_max)
2616
2617 @ConfigSync(shared=1)
2619 """Get the number of current and maximum possible candidates.
2620
2621 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2622
2623 @type exceptions: list
2624 @param exceptions: if passed, list of nodes that should be ignored
2625 @rtype: tuple
2626 @return: tuple of (current, max)
2627
2628 """
2629 return self._UnlockedGetMasterCandidateStats(exceptions)
2630
2631 @ConfigSync()
2632 - def MaintainCandidatePool(self, exception_node_uuids):
2633 """Try to grow the candidate pool to the desired size.
2634
2635 @type exception_node_uuids: list
2636 @param exception_node_uuids: if passed, list of nodes that should be ignored
2637 @rtype: list
2638 @return: list with the adjusted nodes (L{objects.Node} instances)
2639
2640 """
2641 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2642 exception_node_uuids)
2643 mod_list = []
2644 if mc_now < mc_max:
2645 node_list = self._ConfigData().nodes.keys()
2646 random.shuffle(node_list)
2647 for uuid in node_list:
2648 if mc_now >= mc_max:
2649 break
2650 node = self._ConfigData().nodes[uuid]
2651 if (node.master_candidate or node.offline or node.drained or
2652 node.uuid in exception_node_uuids or not node.master_capable):
2653 continue
2654 mod_list.append(node)
2655 node.master_candidate = True
2656 node.serial_no += 1
2657 mc_now += 1
2658 if mc_now != mc_max:
2659
2660 logging.warning("Warning: MaintainCandidatePool didn't manage to"
2661 " fill the candidate pool (%d/%d)", mc_now, mc_max)
2662 if mod_list:
2663 self._ConfigData().cluster.serial_no += 1
2664
2665 return mod_list
2666
2668 """Add a given node to the specified group.
2669
2670 """
2671 if nodegroup_uuid not in self._ConfigData().nodegroups:
2672
2673
2674
2675
2676 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2677 if node_uuid not in self._ConfigData().nodegroups[nodegroup_uuid].members:
2678 self._ConfigData().nodegroups[nodegroup_uuid].members.append(node_uuid)
2679
2681 """Remove a given node from its group.
2682
2683 """
2684 nodegroup = node.group
2685 if nodegroup not in self._ConfigData().nodegroups:
2686 logging.warning("Warning: node '%s' has unknown node group '%s'"
2687 " (while being removed from it)", node.uuid, nodegroup)
2688 nodegroup_obj = self._ConfigData().nodegroups[nodegroup]
2689 if node.uuid not in nodegroup_obj.members:
2690 logging.warning("Warning: node '%s' not a member of its node group '%s'"
2691 " (while being removed from it)", node.uuid, nodegroup)
2692 else:
2693 nodegroup_obj.members.remove(node.uuid)
2694
2695 @ConfigSync()
2697 """Changes the group of a number of nodes.
2698
2699 @type mods: list of tuples; (node name, new group UUID)
2700 @param mods: Node membership modifications
2701
2702 """
2703 groups = self._ConfigData().nodegroups
2704 nodes = self._ConfigData().nodes
2705
2706 resmod = []
2707
2708
2709 for (node_uuid, new_group_uuid) in mods:
2710 try:
2711 node = nodes[node_uuid]
2712 except KeyError:
2713 raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2714
2715 if node.group == new_group_uuid:
2716
2717 logging.debug("Node '%s' was assigned to its current group (%s)",
2718 node_uuid, node.group)
2719 continue
2720
2721
2722 try:
2723 old_group = groups[node.group]
2724 except KeyError:
2725 raise errors.ConfigurationError("Unable to find old group '%s'" %
2726 node.group)
2727
2728
2729 try:
2730 new_group = groups[new_group_uuid]
2731 except KeyError:
2732 raise errors.ConfigurationError("Unable to find new group '%s'" %
2733 new_group_uuid)
2734
2735 assert node.uuid in old_group.members, \
2736 ("Inconsistent configuration: node '%s' not listed in members for its"
2737 " old group '%s'" % (node.uuid, old_group.uuid))
2738 assert node.uuid not in new_group.members, \
2739 ("Inconsistent configuration: node '%s' already listed in members for"
2740 " its new group '%s'" % (node.uuid, new_group.uuid))
2741
2742 resmod.append((node, old_group, new_group))
2743
2744
2745 for (node, old_group, new_group) in resmod:
2746 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2747 "Assigning to current group is not possible"
2748
2749 node.group = new_group.uuid
2750
2751
2752 if node.uuid in old_group.members:
2753 old_group.members.remove(node.uuid)
2754 if node.uuid not in new_group.members:
2755 new_group.members.append(node.uuid)
2756
2757
2758 now = time.time()
2759 for obj in frozenset(itertools.chain(*resmod)):
2760 obj.serial_no += 1
2761 obj.mtime = now
2762
2763
2764 self._ConfigData().cluster.serial_no += 1
2765
2767 """Bump up the serial number of the config.
2768
2769 """
2770 self._ConfigData().serial_no += 1
2771 self._ConfigData().mtime = time.time()
2772
2784
2786 """Returns a ConfigManager, which is suitable to perform a synchronized
2787 block of configuration operations.
2788
2789 WARNING: This blocks all other configuration operations, so anything that
2790 runs inside the block should be very fast, preferably not using any IO.
2791 """
2792
2793 return ConfigManager(self, shared=shared, forcelock=forcelock)
2794
2796 self._lock_count += count
2797 return self._lock_count
2798
2800 return self._lock_count
2801
2803 """Read the config data from WConfd or disk.
2804
2805 """
2806 if self._AddLockCount(1) > 1:
2807 if self._lock_current_shared and not shared:
2808 self._AddLockCount(-1)
2809 raise errors.ConfigurationError("Can't request an exclusive"
2810 " configuration lock while holding"
2811 " shared")
2812 elif not force or self._lock_forced or not shared or self._offline:
2813 return
2814 else:
2815 self._lock_current_shared = shared
2816 if force:
2817 self._lock_forced = True
2818
2819
2820 if self._offline:
2821 try:
2822 raw_data = utils.ReadFile(self._cfg_file)
2823 data_dict = serializer.Load(raw_data)
2824
2825 ValidateConfig(data_dict)
2826 data = objects.ConfigData.FromDict(data_dict)
2827 except errors.ConfigVersionMismatch:
2828 raise
2829 except Exception, err:
2830 raise errors.ConfigurationError(err)
2831
2832 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2833
2834 if (not hasattr(data, "cluster") or
2835 not hasattr(data.cluster, "rsahostkeypub")):
2836 raise errors.ConfigurationError("Incomplete configuration"
2837 " (missing cluster.rsahostkeypub)")
2838
2839 if not data.cluster.master_node in data.nodes:
2840 msg = ("The configuration denotes node %s as master, but does not"
2841 " contain information about this node" %
2842 data.cluster.master_node)
2843 raise errors.ConfigurationError(msg)
2844
2845 master_info = data.nodes[data.cluster.master_node]
2846 if master_info.name != self._my_hostname and not self._accept_foreign:
2847 msg = ("The configuration denotes node %s as master, while my"
2848 " hostname is %s; opening a foreign configuration is only"
2849 " possible in accept_foreign mode" %
2850 (master_info.name, self._my_hostname))
2851 raise errors.ConfigurationError(msg)
2852
2853 self._SetConfigData(data)
2854
2855
2856 self._UpgradeConfig(saveafter=True)
2857 else:
2858 if shared and not force:
2859 if self._config_data is None:
2860 logging.debug("Requesting config, as I have no up-to-date copy")
2861 dict_data = self._wconfd.ReadConfig()
2862 else:
2863 logging.debug("My config copy is up to date.")
2864 dict_data = None
2865 else:
2866
2867 while True:
2868 dict_data = \
2869 self._wconfd.LockConfig(self._GetWConfdContext(), bool(shared))
2870 logging.debug("Received config from WConfd.LockConfig [shared=%s]",
2871 bool(shared))
2872 if dict_data is not None:
2873 break
2874 time.sleep(random.random())
2875
2876 try:
2877 if dict_data is not None:
2878 self._SetConfigData(objects.ConfigData.FromDict(dict_data))
2879 self._UpgradeConfig()
2880 except Exception, err:
2881 raise errors.ConfigurationError(err)
2882
2884 """Release resources relating the config data.
2885
2886 """
2887 if self._AddLockCount(-1) > 0:
2888 return
2889 if save:
2890 try:
2891 logging.debug("Writing configuration and unlocking it")
2892 self._WriteConfig(releaselock=True)
2893 except Exception, err:
2894 logging.critical("Can't write the configuration: %s", str(err))
2895 raise
2896 elif not self._offline and \
2897 not (self._lock_current_shared and not self._lock_forced):
2898 logging.debug("Unlocking configuration without writing")
2899 self._wconfd.UnlockConfig(self._GetWConfdContext())
2900 self._lock_forced = False
2901
2902
2946
2947 - def _WriteConfig(self, destination=None, releaselock=False):
2948 """Write the configuration data to persistent storage.
2949
2950 """
2951 if destination is None:
2952 destination = self._cfg_file
2953
2954
2955
2956 if self._offline:
2957 self._BumpSerialNo()
2958 txt = serializer.DumpJson(
2959 self._ConfigData().ToDict(_with_private=True),
2960 private_encoder=serializer.EncodeWithPrivateFields
2961 )
2962
2963 getents = self._getents()
2964 try:
2965 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2966 close=False, gid=getents.confd_gid, mode=0640)
2967 except errors.LockError:
2968 raise errors.ConfigurationError("The configuration file has been"
2969 " modified since the last write, cannot"
2970 " update")
2971 try:
2972 self._cfg_id = utils.GetFileID(fd=fd)
2973 finally:
2974 os.close(fd)
2975 else:
2976 try:
2977 if releaselock:
2978 res = self._wconfd.WriteConfigAndUnlock(self._GetWConfdContext(),
2979 self._ConfigData().ToDict())
2980 if not res:
2981 logging.warning("WriteConfigAndUnlock indicates we already have"
2982 " released the lock; assuming this was just a retry"
2983 " and the initial call succeeded")
2984 else:
2985 self._wconfd.WriteConfig(self._GetWConfdContext(),
2986 self._ConfigData().ToDict())
2987 except errors.LockError:
2988 raise errors.ConfigurationError("The configuration file has been"
2989 " modified since the last write, cannot"
2990 " update")
2991
2992 self.write_count += 1
2993
2995 """Get the hvparams of all given hypervisors from the config.
2996
2997 @type hypervisors: list of string
2998 @param hypervisors: list of hypervisor names
2999 @rtype: dict of strings
3000 @returns: dictionary mapping the hypervisor name to a string representation
3001 of the hypervisor's hvparams
3002
3003 """
3004 hvparams = {}
3005 for hv in hypervisors:
3006 hvparams[hv] = self._UnlockedGetHvparamsString(hv)
3007 return hvparams
3008
3009 @staticmethod
3011 """Extends the ssconf_values dictionary by hvparams.
3012
3013 @type ssconf_values: dict of strings
3014 @param ssconf_values: dictionary mapping ssconf_keys to strings
3015 representing the content of ssconf files
3016 @type all_hvparams: dict of strings
3017 @param all_hvparams: dictionary mapping hypervisor names to a string
3018 representation of their hvparams
3019 @rtype: same as ssconf_values
3020 @returns: the ssconf_values dictionary extended by hvparams
3021
3022 """
3023 for hv in all_hvparams:
3024 ssconf_key = constants.SS_HVPARAMS_PREF + hv
3025 ssconf_values[ssconf_key] = all_hvparams[hv]
3026 return ssconf_values
3027
3034
3036 """Return the values needed by ssconf.
3037
3038 @rtype: dict
3039 @return: a dictionary with keys the ssconf names and values their
3040 associated value
3041
3042 """
3043 fn = "\n".join
3044 instance_names = utils.NiceSort(
3045 [inst.name for inst in
3046 self._UnlockedGetAllInstancesInfo().values()])
3047 node_infos = self._UnlockedGetAllNodesInfo().values()
3048 node_names = [node.name for node in node_infos]
3049 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
3050 for ninfo in node_infos]
3051 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
3052 for ninfo in node_infos]
3053 node_vm_capable = ["%s=%s" % (ninfo.name, str(ninfo.vm_capable))
3054 for ninfo in node_infos]
3055
3056 instance_data = fn(instance_names)
3057 off_data = fn(node.name for node in node_infos if node.offline)
3058 on_data = fn(node.name for node in node_infos if not node.offline)
3059 mc_data = fn(node.name for node in node_infos if node.master_candidate)
3060 mc_ips_data = fn(node.primary_ip for node in node_infos
3061 if node.master_candidate)
3062 node_data = fn(node_names)
3063 node_pri_ips_data = fn(node_pri_ips)
3064 node_snd_ips_data = fn(node_snd_ips)
3065 node_vm_capable_data = fn(node_vm_capable)
3066
3067 cluster = self._ConfigData().cluster
3068 cluster_tags = fn(cluster.GetTags())
3069
3070 master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
3071 for mc_uuid, mc_cert
3072 in cluster.candidate_certs.items())
3073
3074 hypervisor_list = fn(cluster.enabled_hypervisors)
3075 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
3076
3077 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
3078
3079 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
3080 self._ConfigData().nodegroups.values()]
3081 nodegroups_data = fn(utils.NiceSort(nodegroups))
3082 networks = ["%s %s" % (net.uuid, net.name) for net in
3083 self._ConfigData().networks.values()]
3084 networks_data = fn(utils.NiceSort(networks))
3085
3086 ssh_ports = fn("%s=%s" % (node_name, port)
3087 for node_name, port
3088 in self._UnlockedGetSshPortMap(node_infos).items())
3089
3090 ssconf_values = {
3091 constants.SS_CLUSTER_NAME: cluster.cluster_name,
3092 constants.SS_CLUSTER_TAGS: cluster_tags,
3093 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
3094 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
3095 constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
3096 constants.SS_MASTER_CANDIDATES: mc_data,
3097 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
3098 constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
3099 constants.SS_MASTER_IP: cluster.master_ip,
3100 constants.SS_MASTER_NETDEV: cluster.master_netdev,
3101 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
3102 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
3103 constants.SS_NODE_LIST: node_data,
3104 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
3105 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
3106 constants.SS_NODE_VM_CAPABLE: node_vm_capable_data,
3107 constants.SS_OFFLINE_NODES: off_data,
3108 constants.SS_ONLINE_NODES: on_data,
3109 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
3110 constants.SS_INSTANCE_LIST: instance_data,
3111 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
3112 constants.SS_HYPERVISOR_LIST: hypervisor_list,
3113 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
3114 constants.SS_UID_POOL: uid_pool,
3115 constants.SS_NODEGROUPS: nodegroups_data,
3116 constants.SS_NETWORKS: networks_data,
3117 constants.SS_ENABLED_USER_SHUTDOWN: str(cluster.enabled_user_shutdown),
3118 constants.SS_SSH_PORTS: ssh_ports,
3119 }
3120 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
3121 all_hvparams)
3122 bad_values = [(k, v) for k, v in ssconf_values.items()
3123 if not isinstance(v, (str, basestring))]
3124 if bad_values:
3125 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
3126 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
3127 " values: %s" % err)
3128 return ssconf_values
3129
3130 @ConfigSync(shared=1)
3136
3137 @ConfigSync(shared=1)
3139 """Return the volume group name.
3140
3141 """
3142 return self._ConfigData().cluster.volume_group_name
3143
3144 @ConfigSync()
3151
3152 @ConfigSync(shared=1)
3154 """Return DRBD usermode helper.
3155
3156 """
3157 return self._ConfigData().cluster.drbd_usermode_helper
3158
3159 @ConfigSync()
3166
3167 @ConfigSync(shared=1)
3169 """Return the mac prefix.
3170
3171 """
3172 return self._ConfigData().cluster.mac_prefix
3173
3174 @ConfigSync(shared=1)
3176 """Returns information about the cluster
3177
3178 @rtype: L{objects.Cluster}
3179 @return: the cluster object
3180
3181 """
3182 return self._ConfigData().cluster
3183
3184 @ConfigSync(shared=1)
3186 """Check if in there is at disk of the given type in the configuration.
3187
3188 """
3189 return self._ConfigData().DisksOfType(dev_type)
3190
3191 @ConfigSync(shared=1)
3193 """Returns a detached version of a ConfigManager, which represents
3194 a read-only snapshot of the configuration at this particular time.
3195
3196 """
3197 return DetachedConfig(self._ConfigData())
3198
3199 @ConfigSync()
3200 - def Update(self, target, feedback_fn, ec_id=None):
3201 """Notify function to be called after updates.
3202
3203 This function must be called when an object (as returned by
3204 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
3205 caller wants the modifications saved to the backing store. Note
3206 that all modified objects will be saved, but the target argument
3207 is the one the caller wants to ensure that it's saved.
3208
3209 @param target: an instance of either L{objects.Cluster},
3210 L{objects.Node} or L{objects.Instance} which is existing in
3211 the cluster
3212 @param feedback_fn: Callable feedback function
3213
3214 """
3215 if self._ConfigData() is None:
3216 raise errors.ProgrammerError("Configuration file not read,"
3217 " cannot save.")
3218
3219 def check_serial(target, current):
3220 if current is None:
3221 raise errors.ConfigurationError("Configuration object unknown")
3222 elif current.serial_no != target.serial_no:
3223 raise errors.ConfigurationError("Configuration object updated since"
3224 " it has been read: %d != %d",
3225 current.serial_no, target.serial_no)
3226
3227 def replace_in(target, tdict):
3228 check_serial(target, tdict.get(target.uuid))
3229 tdict[target.uuid] = target
3230
3231 update_serial = False
3232 if isinstance(target, objects.Cluster):
3233 check_serial(target, self._ConfigData().cluster)
3234 self._ConfigData().cluster = target
3235 elif isinstance(target, objects.Node):
3236 replace_in(target, self._ConfigData().nodes)
3237 update_serial = True
3238 elif isinstance(target, objects.Instance):
3239 replace_in(target, self._ConfigData().instances)
3240 elif isinstance(target, objects.NodeGroup):
3241 replace_in(target, self._ConfigData().nodegroups)
3242 elif isinstance(target, objects.Network):
3243 replace_in(target, self._ConfigData().networks)
3244 elif isinstance(target, objects.Disk):
3245 replace_in(target, self._ConfigData().disks)
3246 else:
3247 raise errors.ProgrammerError("Invalid object type (%s) passed to"
3248 " ConfigWriter.Update" % type(target))
3249 target.serial_no += 1
3250 target.mtime = now = time.time()
3251
3252 if update_serial:
3253
3254 self._ConfigData().cluster.serial_no += 1
3255 self._ConfigData().cluster.mtime = now
3256
3257 if isinstance(target, objects.Disk):
3258 self._UnlockedReleaseDRBDMinors(target.uuid)
3259
3260 if ec_id is not None:
3261
3262
3263
3264 self._UnlockedCommitTemporaryIps(ec_id)
3265
3266
3267
3268 self._UnlockedVerifyConfigAndLog(feedback_fn=feedback_fn)
3269
3280
3283
3284 @ConfigSync(shared=1)
3286 """Get configuration info of all the networks.
3287
3288 """
3289 return dict(self._ConfigData().networks)
3290
3292 """Get the list of networks.
3293
3294 This function is for internal use, when the config lock is already held.
3295
3296 """
3297 return self._ConfigData().networks.keys()
3298
3299 @ConfigSync(shared=1)
3301 """Get the list of networks.
3302
3303 @return: array of networks, ex. ["main", "vlan100", "200]
3304
3305 """
3306 return self._UnlockedGetNetworkList()
3307
3308 @ConfigSync(shared=1)
3310 """Get a list of network names
3311
3312 """
3313 names = [net.name
3314 for net in self._ConfigData().networks.values()]
3315 return names
3316
3318 """Returns information about a network.
3319
3320 This function is for internal use, when the config lock is already held.
3321
3322 """
3323 if uuid not in self._ConfigData().networks:
3324 return None
3325
3326 return self._ConfigData().networks[uuid]
3327
3328 @ConfigSync(shared=1)
3330 """Returns information about a network.
3331
3332 It takes the information from the configuration file.
3333
3334 @param uuid: UUID of the network
3335
3336 @rtype: L{objects.Network}
3337 @return: the network object
3338
3339 """
3340 return self._UnlockedGetNetwork(uuid)
3341
3342 @ConfigSync()
3343 - def AddNetwork(self, net, ec_id, check_uuid=True):
3344 """Add a network to the configuration.
3345
3346 @type net: L{objects.Network}
3347 @param net: the Network object to add
3348 @type ec_id: string
3349 @param ec_id: unique id for the job to use when creating a missing UUID
3350
3351 """
3352 self._UnlockedAddNetwork(net, ec_id, check_uuid)
3353
3355 """Add a network to the configuration.
3356
3357 """
3358 logging.info("Adding network %s to configuration", net.name)
3359
3360 if check_uuid:
3361 self._EnsureUUID(net, ec_id)
3362
3363 net.serial_no = 1
3364 net.ctime = net.mtime = time.time()
3365 self._ConfigData().networks[net.uuid] = net
3366 self._ConfigData().cluster.serial_no += 1
3367
3369 """Lookup a network's UUID.
3370
3371 @type target: string
3372 @param target: network name or UUID
3373 @rtype: string
3374 @return: network UUID
3375 @raises errors.OpPrereqError: when the target network cannot be found
3376
3377 """
3378 if target is None:
3379 return None
3380 if target in self._ConfigData().networks:
3381 return target
3382 for net in self._ConfigData().networks.values():
3383 if net.name == target:
3384 return net.uuid
3385 raise errors.OpPrereqError("Network '%s' not found" % target,
3386 errors.ECODE_NOENT)
3387
3388 @ConfigSync(shared=1)
3390 """Lookup a network's UUID.
3391
3392 This function is just a wrapper over L{_UnlockedLookupNetwork}.
3393
3394 @type target: string
3395 @param target: network name or UUID
3396 @rtype: string
3397 @return: network UUID
3398
3399 """
3400 return self._UnlockedLookupNetwork(target)
3401
3402 @ConfigSync()
3404 """Remove a network from the configuration.
3405
3406 @type network_uuid: string
3407 @param network_uuid: the UUID of the network to remove
3408
3409 """
3410 logging.info("Removing network %s from configuration", network_uuid)
3411
3412 if network_uuid not in self._ConfigData().networks:
3413 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
3414
3415 del self._ConfigData().networks[network_uuid]
3416 self._ConfigData().cluster.serial_no += 1
3417
3419 """Get the netparams (mode, link) of a network.
3420
3421 Get a network's netparams for a given node.
3422
3423 @type net_uuid: string
3424 @param net_uuid: network uuid
3425 @type node_uuid: string
3426 @param node_uuid: node UUID
3427 @rtype: dict or None
3428 @return: netparams
3429
3430 """
3431 node_info = self._UnlockedGetNodeInfo(node_uuid)
3432 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
3433 netparams = nodegroup_info.networks.get(net_uuid, None)
3434
3435 return netparams
3436
3437 @ConfigSync(shared=1)
3439 """Locking wrapper of _UnlockedGetGroupNetParams()
3440
3441 """
3442 return self._UnlockedGetGroupNetParams(net_uuid, node_uuid)
3443
3444 @ConfigSync(shared=1)
3446 """Check IP uniqueness in nodegroup.
3447
3448 Check networks that are connected in the node's node group
3449 if ip is contained in any of them. Used when creating/adding
3450 a NIC to ensure uniqueness among nodegroups.
3451
3452 @type ip: string
3453 @param ip: ip address
3454 @type node_uuid: string
3455 @param node_uuid: node UUID
3456 @rtype: (string, dict) or (None, None)
3457 @return: (network name, netparams)
3458
3459 """
3460 if ip is None:
3461 return (None, None)
3462 node_info = self._UnlockedGetNodeInfo(node_uuid)
3463 nodegroup_info = self._UnlockedGetNodeGroup(node_info.group)
3464 for net_uuid in nodegroup_info.networks.keys():
3465 net_info = self._UnlockedGetNetwork(net_uuid)
3466 pool = network.AddressPool(net_info)
3467 if pool.Contains(ip):
3468 return (net_info.name, nodegroup_info.networks[net_uuid])
3469
3470 return (None, None)
3471
3472 @ConfigSync(shared=1)
3474 """Returns the candidate certificate map.
3475
3476 """
3477 return self._ConfigData().cluster.candidate_certs
3478
3479 @ConfigSync()
3481 """Replaces the master candidate cert list with the new values.
3482
3483 @type certs: dict of string to string
3484 @param certs: map of node UUIDs to SSL client certificate digests.
3485
3486 """
3487 self._ConfigData().cluster.candidate_certs = certs
3488
3489 @ConfigSync()
3492 """Adds an entry to the candidate certificate map.
3493
3494 @type node_uuid: string
3495 @param node_uuid: the node's UUID
3496 @type cert_digest: string
3497 @param cert_digest: the digest of the node's client SSL certificate
3498 @type info_fn: function
3499 @param info_fn: logging function for information messages
3500 @type warn_fn: function
3501 @param warn_fn: logging function for warning messages
3502
3503 """
3504 cluster = self._ConfigData().cluster
3505 if node_uuid in cluster.candidate_certs:
3506 old_cert_digest = cluster.candidate_certs[node_uuid]
3507 if old_cert_digest == cert_digest:
3508 if info_fn is not None:
3509 info_fn("Certificate digest for node %s already in config."
3510 "Not doing anything." % node_uuid)
3511 return
3512 else:
3513 if warn_fn is not None:
3514 warn_fn("Overriding differing certificate digest for node %s"
3515 % node_uuid)
3516 cluster.candidate_certs[node_uuid] = cert_digest
3517
3518 @ConfigSync()
3521 """Removes the entry of the given node in the certificate map.
3522
3523 @type node_uuid: string
3524 @param node_uuid: the node's UUID
3525 @type warn_fn: function
3526 @param warn_fn: logging function for warning messages
3527
3528 """
3529 cluster = self._ConfigData().cluster
3530 if node_uuid not in cluster.candidate_certs:
3531 if warn_fn is not None:
3532 warn_fn("Cannot remove certifcate for node %s, because it's not"
3533 " in the candidate map." % node_uuid)
3534 return
3535 del cluster.candidate_certs[node_uuid]
3536
3538 """Force the distribution of configuration to master candidates.
3539
3540 It is not necessary to hold a lock for this operation, it is handled
3541 internally by WConfd.
3542
3543 """
3544 if not self._offline:
3545 self._wconfd.FlushConfig()
3546
3547 @ConfigSync(shared=1)
3549 """Get the configuration of all disks.
3550
3551 @rtype: dict
3552 @return: dict of (disk, disk_info), where disk_info is what
3553 would GetDiskInfo return for disk
3554 """
3555 return self._UnlockedGetAllDiskInfo()
3556
3560
3561 @ConfigSync(shared=1)
3563 """Returns the instance the disk is currently attached to.
3564
3565 @type disk_uuid: string
3566 @param disk_uuid: the identifier of the disk in question.
3567
3568 @rtype: string
3569 @return: uuid of instance the disk is attached to.
3570 """
3571 for inst_uuid, inst_info in self._UnlockedGetAllInstancesInfo().items():
3572 if disk_uuid in inst_info.disks:
3573 return inst_uuid
3574
3577 """Read-only snapshot of the config."""
3578
3582
3583 @staticmethod
3587
3591
3595