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