Do not add a new Inotify watchers on timer
[ganeti-github.git] / lib / config / __init__.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 # pylint: disable=R0904
44 # R0904: Too many public methods
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
93
94 def GetConfig(ec_id, livelock, **kwargs):
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 # if the config is to be opened in the accept_foreign mode, we should
112 # also tell the RPC client not to check for the master node
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 # job id used for resource management at config upgrade time
120 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
121
122
123 def _MatchNameComponentIgnoreCase(short_name, names):
124 """Wrapper around L{utils.text.MatchNameComponent}.
125
126 """
127 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
128
129
130 def _CheckInstanceDiskIvNames(disks):
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
149
150 def _UpdateIvNames(base_idx, disks):
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
159
160 class ConfigWriter(object):
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 # Note: in order to prevent errors when resolving our name later,
188 # we compute it here once and reuse it; it's
189 # better to raise an error before starting to modify the config
190 # file than after it was modified
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
200 def _ConfigData(self):
201 return self._config_data
202
203 def OutDate(self):
204 self._config_data = None
205
206 def _SetConfigData(self, cfg):
207 self._config_data = cfg
208
209 def _GetWConfdContext(self):
210 return self._wconfdcontext
211
212 # this method needs to be static, so that we can call it on the class
213 @staticmethod
214 def IsCluster():
215 """Check if the cluster is configured.
216
217 """
218 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
219
220 def _UnlockedGetNdParams(self, node):
221 nodegroup = self._UnlockedGetNodeGroup(node.group)
222 return self._ConfigData().cluster.FillND(node, nodegroup)
223
224 @ConfigSync(shared=1)
225 def GetNdParams(self, node):
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)
236 def GetNdGroupParams(self, nodegroup):
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
246 def _UnlockedGetNdGroupParams(self, group):
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)
258 def GetGroupSshPorts(self):
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)
273 def GetInstanceDiskParams(self, instance):
274 """Get the disk params populated with inherit chain.
275
276 @type instance: L{objects.Instance}
277 @param instance: The instance we want to know the params for
278 @return: A dict with the filled in disk params
279
280 """
281 node = self._UnlockedGetNodeInfo(instance.primary_node)
282 nodegroup = self._UnlockedGetNodeGroup(node.group)
283 return self._UnlockedGetGroupDiskParams(nodegroup)
284
285 def _UnlockedGetInstanceDisks(self, inst_uuid):
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)
303 def GetInstanceDisks(self, inst_uuid):
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
311 def _UnlockedAddDisk(self, disk, replace=False):
312 """Add a disk to the config.
313
314 @type disk: L{objects.Disk}
315 @param disk: The disk object
316
317 """
318 if not isinstance(disk, objects.Disk):
319 raise errors.ProgrammerError("Invalid type passed to _UnlockedAddDisk")
320
321 logging.info("Adding disk %s to configuration", disk.uuid)
322
323 if replace:
324 self._CheckUUIDpresent(disk)
325 else:
326 self._CheckUniqueUUID(disk, include_temporary=False)
327 disk.serial_no = 1
328 disk.ctime = disk.mtime = time.time()
329 disk.UpgradeConfig()
330 self._ConfigData().disks[disk.uuid] = disk
331 self._ConfigData().cluster.serial_no += 1
332 self._UnlockedReleaseDRBDMinors(disk.uuid)
333
334 def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
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 # Disk must not be attached anywhere else
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()
375 def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False):
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()
386 def AttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
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
394 def _UnlockedDetachInstanceDisk(self, inst_uuid, disk_uuid):
395 """Detach a disk from an instance.
396
397 @type inst_uuid: string
398 @param inst_uuid: The UUID of the instance object
399 @type disk_uuid: string
400 @param disk_uuid: The UUID of the disk object
401
402 """
403 instance = self._UnlockedGetInstanceInfo(inst_uuid)
404 if instance is None:
405 raise errors.ConfigurationError("Instance %s doesn't exist"
406 % inst_uuid)
407 if disk_uuid not in self._ConfigData().disks:
408 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)
409
410 # Check if disk is attached to the instance
411 if disk_uuid not in instance.disks:
412 raise errors.ProgrammerError("Disk %s is not attached to an instance"
413 % disk_uuid)
414
415 idx = instance.disks.index(disk_uuid)
416 instance.disks.remove(disk_uuid)
417 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
418 _UpdateIvNames(idx, instance_disks[idx:])
419 instance.serial_no += 1
420 instance.mtime = time.time()
421
422 def _UnlockedRemoveDisk(self, disk_uuid):
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 # Disk must not be attached anywhere
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 # Remove disk from config file
440 del self._ConfigData().disks[disk_uuid]
441 self._ConfigData().cluster.serial_no += 1
442
443 @ConfigSync()
444 def RemoveInstanceDisk(self, inst_uuid, disk_uuid):
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()
455 def DetachInstanceDisk(self, inst_uuid, disk_uuid):
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
462 def _UnlockedGetDiskInfo(self, disk_uuid):
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)
479 def GetDiskInfo(self, disk_uuid):
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
487 def _UnlockedGetDiskInfoByName(self, disk_name):
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)
513 def GetDiskInfoByName(self, disk_name):
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
521 def _UnlockedGetDiskList(self):
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)
530 def GetAllDisksInfo(self):
531 """Get the configuration of all disks.
532
533 This is a simple wrapper over L{_UnlockedGetAllDisksInfo}.
534
535 """
536 return self._UnlockedGetAllDisksInfo()
537
538 def _UnlockedGetAllDisksInfo(self):
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
550 def _AllInstanceNodes(self, inst_uuid):
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
572 def _UnlockedGetInstanceNodes(self, inst_uuid):
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 # ensure that primary node is always the first
587 all_nodes.discard(instance.primary_node)
588 return (instance.primary_node, ) + tuple(all_nodes)
589
590 @ConfigSync(shared=1)
591 def GetInstanceNodes(self, inst_uuid):
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
599 def _UnlockedGetInstanceSecondaryNodes(self, inst_uuid):
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)
613 def GetInstanceSecondaryNodes(self, inst_uuid):
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
621 def _UnlockedGetInstanceLVsByNode(self, inst_uuid, lvmap=None):
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)
671 def GetInstanceLVsByNode(self, inst_uuid, lvmap=None):
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)
680 def GetGroupDiskParams(self, group):
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
690 def _UnlockedGetGroupDiskParams(self, group):
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)
703 def GetPotentialMasterCandidates(self):
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 # FIXME: Note that currently potential master candidates are nodes
711 # but this definition will be extended once RAPI-unmodifiable
712 # parameters are introduced.
713 nodes = self._UnlockedGetAllNodesInfo()
714 return [node_info.name for node_info in nodes.values()]
715
716 def GenerateMAC(self, net_uuid, _ec_id):
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
724 def ReserveMAC(self, mac, _ec_id):
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
733 def _UnlockedCommitTemporaryIps(self, _ec_id):
734 """Commit all reserved IP address to their respective pools
735
736 """
737 if self._offline:
738 raise errors.ProgrammerError("Can't call CommitTemporaryIps"
739 " in offline mode")
740 ips = self._wconfd.ListReservedIps(self._GetWConfdContext())
741 for action, address, net_uuid in ips:
742 self._UnlockedCommitIp(action, net_uuid, address)
743
744 def _UnlockedCommitIp(self, action, net_uuid, address):
745 """Commit a reserved IP address to an IP pool.
746
747 The IP address is taken from the network's IP pool and marked as free.
748
749 """
750 nobj = self._UnlockedGetNetwork(net_uuid)
751 if nobj is None:
752 raise errors.ProgrammerError("Network '%s' not found" % (net_uuid, ))
753 pool = network.AddressPool(nobj)
754 if action == constants.RESERVE_ACTION:
755 pool.Reserve(address)
756 elif action == constants.RELEASE_ACTION:
757 pool.Release(address)
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
770 def GenerateIp(self, net_uuid, _ec_id):
771 """Find a free IPv4 address for an instance.
772
773 """
774 if self._offline:
775 raise errors.ProgrammerError("Can't call GenerateIp in offline mode")
776 return self._wconfd.GenerateIp(self._GetWConfdContext(), net_uuid)
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
787 def ReserveLV(self, lv_name, _ec_id):
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
796 def GenerateDRBDSecret(self, _ec_id):
797 """Generate a DRBD secret.
798
799 This checks the current disks for duplicates.
800
801 """
802 return self._wconfd.GenerateDRBDSecret(self._GetWConfdContext())
803
804 # FIXME: After _AllIDs is removed, move it to config_mock.py
805 def _AllLVs(self):
806 """Compute the list of all LVs.
807
808 """
809 lvnames = set()
810 for instance in self._ConfigData().instances.values():
811 node_data = self._UnlockedGetInstanceLVsByNode(instance.uuid)
812 for lv_list in node_data.values():
813 lvnames.update(lv_list)
814 return lvnames
815
816 def _AllNICs(self):
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
843 def _GenerateUniqueID(self, ec_id):
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)
857 def GenerateUniqueID(self, ec_id):
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
868 def _AllMACs(self):
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
882 def _AllDRBDSecrets(self):
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
904 def _VerifyDisks(data, result):
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
924 def _UnlockedVerifyConfig(self):
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 # pylint: disable=R0914
933 result = []
934 seen_macs = []
935 ports = {}
936 data = self._ConfigData()
937 cluster = data.cluster
938
939 # First call WConfd to perform its checks, if we're not offline
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 # check cluster parameters
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 # per-instance checks
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 # parameter checks
1003 if instance.beparams:
1004 VerifyType("instance %s" % instance.name, "beparams",
1005 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES,
1006 result.append)
1007
1008 # check that disks exists
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 # gather the drbd ports for duplicate checks
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 # gather network port reservation
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 # cluster-wide pool of free ports
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 # compute tcp/udp duplicate ports
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 # highest used tcp port check
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 # master candidate checks
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 # node checks
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 # nodegroups checks
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 # drbd minors check
1113 # FIXME: The check for DRBD map needs to be implemented in WConfd
1114
1115 # IP checks
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
1157 def _UnlockedVerifyConfigAndLog(self, feedback_fn=None):
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 # Warn on config errors, but don't abort the save - the
1172 # configuration has already been modified, and we can't revert;
1173 # the best we can do is to warn the user and save as is, leaving
1174 # recovery to the user
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)
1185 def VerifyConfig(self):
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()
1198 def AddTcpUdpPort(self, port):
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)
1212 def GetPortList(self):
1213 """Returns a copy of the current port list.
1214
1215 """
1216 return self._ConfigData().cluster.tcpudp_port_pool.copy()
1217
1218 @ConfigSync()
1219 def AllocatePort(self):
1220 """Allocate a port.
1221
1222 The port will be taken from the available port pool or from the
1223 default port range (and in this case we increase
1224 highest_used_port).
1225
1226 """
1227 # If there are TCP/IP ports configured, we use them first.
1228 if self._ConfigData().cluster.tcpudp_port_pool:
1229 port = self._ConfigData().cluster.tcpudp_port_pool.pop()
1230 else:
1231 port = self._ConfigData().cluster.highest_used_port + 1
1232 if port >= constants.LAST_DRBD_PORT:
1233 raise errors.ConfigurationError("The highest used port is greater"
1234 " than %s. Aborting." %
1235 constants.LAST_DRBD_PORT)
1236 self._ConfigData().cluster.highest_used_port = port
1237 return port
1238
1239 @ConfigSync(shared=1)
1240 def ComputeDRBDMap(self):
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
1256 def AllocateDRBDMinor(self, node_uuids, disk_uuid):
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
1286 def _UnlockedReleaseDRBDMinors(self, disk_uuid):
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 # in offline mode we allow the calls to release DRBD minors,
1298 # because then nothing can be allocated anyway;
1299 # this is useful for testing
1300 if not self._offline:
1301 self._wconfd.ReleaseDRBDMinors(disk_uuid)
1302
1303 @ConfigSync()
1304 def ReleaseDRBDMinors(self, disk_uuid):
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)
1320 def GetInstanceDiskTemplate(self, inst_uuid):
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)
1334 def GetConfigVersion(self):
1335 """Get the configuration version.
1336
1337 @return: Config version
1338
1339 """
1340 return self._ConfigData().version
1341
1342 @ConfigSync(shared=1)
1343 def GetClusterName(self):
1344 """Get cluster name.
1345
1346 @return: Cluster name
1347
1348 """
1349 return self._ConfigData().cluster.cluster_name
1350
1351 @ConfigSync(shared=1)
1352 def GetMasterNode(self):
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)
1361 def GetMasterNodeName(self):
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)
1370 def GetMasterNodeInfo(self):
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)
1380 def GetMasterIP(self):
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)
1389 def GetMasterNetdev(self):
1390 """Get the master network device for this cluster.
1391
1392 """
1393 return self._ConfigData().cluster.master_netdev
1394
1395 @ConfigSync(shared=1)
1396 def GetMasterNetmask(self):
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)
1403 def GetUseExternalMipScript(self):
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)
1410 def GetFileStorageDir(self):
1411 """Get the file storage dir for this cluster.
1412
1413 """
1414 return self._ConfigData().cluster.file_storage_dir
1415
1416 @ConfigSync(shared=1)
1417 def GetSharedFileStorageDir(self):
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)
1424 def GetGlusterStorageDir(self):
1425 """Get the Gluster storage dir for this cluster.
1426
1427 """
1428 return self._ConfigData().cluster.gluster_storage_dir
1429
1430 @ConfigSync(shared=1)
1431 def GetHypervisorType(self):
1432 """Get the hypervisor type for this cluster.
1433
1434 """
1435 return self._ConfigData().cluster.enabled_hypervisors[0]
1436
1437 @ConfigSync(shared=1)
1438 def GetRsaHostKey(self):
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)
1448 def GetDsaHostKey(self):
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)
1458 def GetDefaultIAllocator(self):
1459 """Get the default instance allocator for this cluster.
1460
1461 """
1462 return self._ConfigData().cluster.default_iallocator
1463
1464 @ConfigSync(shared=1)
1465 def GetDefaultIAllocatorParameters(self):
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)
1475 def GetPrimaryIPFamily(self):
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)
1484 def GetMasterNetworkParameters(self):
1485 """Get network parameters of the master node.
1486
1487 @rtype: L{object.MasterNetworkParameters}
1488 @return: network parameters of the master node
1489
1490 """
1491 cluster = self._ConfigData().cluster
1492 result = objects.MasterNetworkParameters(
1493 uuid=cluster.master_node, ip=cluster.master_ip,
1494 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1495 ip_family=cluster.primary_ip_family)
1496
1497 return result
1498
1499 @ConfigSync(shared=1)
1500 def GetInstallImage(self):
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()
1510 def SetInstallImage(self, install_image):
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)
1520 def GetInstanceCommunicationNetwork(self):
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()
1531 def SetInstanceCommunicationNetwork(self, network_name):
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)
1542 def GetZeroingImage(self):
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)
1552 def GetCompressionTools(self):
1553 """Get cluster compression tools
1554
1555 @rtype: list of string
1556 @return: a list of tools that are cleared for use in this cluster for the
1557 purpose of compressing data
1558
1559 """
1560 return self._ConfigData().cluster.compression_tools
1561
1562 @ConfigSync()
1563 def SetCompressionTools(self, tools):
1564 """Set cluster compression tools
1565
1566 @type tools: list of string
1567 @param tools: a list of tools that are cleared for use in this cluster for
1568 the purpose of compressing data
1569
1570 """
1571 self._ConfigData().cluster.compression_tools = tools
1572
1573 @ConfigSync()
1574 def AddNodeGroup(self, group, ec_id, check_uuid=True):
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
1592 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1593 """Add a node group to the configuration.
1594
1595 """
1596 logging.info("Adding node group %s to configuration", group.name)
1597
1598 # Some code might need to add a node group with a pre-populated UUID
1599 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1600 # the "does this UUID" exist already check.
1601 if check_uuid:
1602 self._EnsureUUID(group, ec_id)
1603
1604 try:
1605 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1606 except errors.OpPrereqError:
1607 pass
1608 else:
1609 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1610 " node group (UUID: %s)" %
1611 (group.name, existing_uuid),
1612 errors.ECODE_EXISTS)
1613
1614 group.serial_no = 1
1615 group.ctime = group.mtime = time.time()
1616 group.UpgradeConfig()
1617
1618 self._ConfigData().nodegroups[group.uuid] = group
1619 self._ConfigData().cluster.serial_no += 1
1620
1621 @ConfigSync()
1622 def RemoveNodeGroup(self, group_uuid):
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
1640 def _UnlockedLookupNodeGroup(self, target):
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)
1665 def LookupNodeGroup(self, target):
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
1678 def _UnlockedGetNodeGroup(self, uuid):
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)
1693 def GetNodeGroup(self, uuid):
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
1704 def _UnlockedGetAllNodeGroupsInfo(self):
1705 """Get the configuration of all node groups.
1706
1707 """
1708 return dict(self._ConfigData().nodegroups)
1709
1710 @ConfigSync(shared=1)
1711 def GetAllNodeGroupsInfo(self):
1712 """Get the configuration of all node groups.
1713
1714 """
1715 return self._UnlockedGetAllNodeGroupsInfo()
1716
1717 @ConfigSync(shared=1)
1718 def GetAllNodeGroupsInfoDict(self):
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)
1727 def GetNodeGroupList(self):
1728 """Get a list of node groups.
1729
1730 """
1731 return self._ConfigData().nodegroups.keys()
1732
1733 @ConfigSync(shared=1)
1734 def GetNodeGroupMembersByNodes(self, nodes):
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)
1745 def GetMultiNodeGroupInfo(self, group_uuids):
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):
1756 """Add an instance to the config.
1757
1758 This should be used after creating a new instance.
1759
1760 @type instance: L{objects.Instance}
1761 @param instance: the instance object
1762 @type replace: bool
1763 @param replace: if true, expect the instance to be present and
1764 replace rather than add.
1765
1766 """
1767 if not isinstance(instance, objects.Instance):
1768 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1769
1770 all_macs = self._AllMACs()
1771 for nic in instance.nics:
1772 if nic.mac in all_macs:
1773 raise errors.ConfigurationError("Cannot add instance %s:"
1774 " MAC address '%s' already in use." %
1775 (instance.name, nic.mac))
1776
1777 if replace:
1778 self._CheckUUIDpresent(instance)
1779 else:
1780 self._CheckUniqueUUID(instance, include_temporary=False)
1781
1782 instance.serial_no = 1
1783 instance.ctime = instance.mtime = time.time()
1784
1785 utils.SimpleRetry(True, self._wconfd.AddInstance, 0.1, 30,
1786 args=[instance.ToDict(), self._GetWConfdContext()])
1787 self.OutDate()
1788
1789 def _EnsureUUID(self, item, ec_id):
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
1801 def _CheckUniqueUUID(self, item, include_temporary):
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
1816 def _CheckUUIDpresent(self, item):
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
1829 def _SetInstanceStatus(self, inst_uuid, status, disks_active,
1830 admin_state_source):
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()
1863 def MarkInstanceUp(self, inst_uuid):
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()
1876 def MarkInstanceOffline(self, inst_uuid):
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()
1889 def RemoveInstance(self, inst_uuid):
1890 """Remove the instance from the configuration.
1891
1892 """
1893 if inst_uuid not in self._ConfigData().instances:
1894 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1895
1896 # If a network port has been allocated to the instance,
1897 # return it to the pool of free ports.
1898 inst = self._ConfigData().instances[inst_uuid]
1899 network_port = getattr(inst, "network_port", None)
1900 if network_port is not None:
1901 self._ConfigData().cluster.tcpudp_port_pool.add(network_port)
1902
1903 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1904
1905 # FIXME: After RemoveInstance is moved to WConfd, use its internal
1906 # function from TempRes module.
1907 for nic in instance.nics:
1908 if nic.network and nic.ip:
1909 # Return all IP addresses to the respective address pools
1910 self._UnlockedCommitIp(constants.RELEASE_ACTION, nic.network, nic.ip)
1911
1912 del self._ConfigData().instances[inst_uuid]
1913 self._ConfigData().cluster.serial_no += 1
1914
1915 @ConfigSync()
1916 def RenameInstance(self, inst_uuid, new_name):
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 # rename the file paths in logical and physical id
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 # Force update of ssconf files
1940 self._ConfigData().cluster.serial_no += 1
1941
1942 @ConfigSync()
1943 def MarkInstanceDown(self, inst_uuid):
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()
1957 def MarkInstanceUserDown(self, inst_uuid):
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()
1969 def MarkInstanceDisksActive(self, inst_uuid):
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()
1979 def MarkInstanceDisksInactive(self, inst_uuid):
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
1988 def _UnlockedGetInstanceList(self):
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)
1997 def GetInstanceList(self):
1998 """Get the list of instances.
1999
2000 @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
2001
2002 """
2003 return self._UnlockedGetInstanceList()
2004
2005 def ExpandInstanceName(self, short_name):
2006 """Attempt to expand an incomplete instance name.
2007
2008 """
2009 # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
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 # there has to be exactly one instance with that name
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
2021 def _UnlockedGetInstanceInfo(self, inst_uuid):
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)
2033 def GetInstanceInfo(self, inst_uuid):
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)
2048 def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
2049 """Returns set of node group UUIDs for instance's nodes.
2050
2051 @rtype: frozenset
2052
2053 """
2054 instance = self._UnlockedGetInstanceInfo(inst_uuid)
2055 if not instance:
2056 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
2057
2058 if primary_only:
2059 nodes = [instance.primary_node]
2060 else:
2061 nodes = self._UnlockedGetInstanceNodes(instance.uuid)
2062
2063 return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
2064 for node_uuid in nodes)
2065
2066 @ConfigSync(shared=1)
2067 def GetInstanceNetworks(self, inst_uuid):
2068 """Returns set of network UUIDs for instance's nics.
2069
2070 @rtype: frozenset
2071
2072 """
2073 instance = self._UnlockedGetInstanceInfo(inst_uuid)
2074 if not instance:
2075 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
2076
2077 networks = set()
2078 for nic in instance.nics:
2079 if nic.network:
2080 networks.add(nic.network)
2081
2082 return frozenset(networks)
2083
2084 @ConfigSync(shared=1)
2085 def GetMultiInstanceInfo(self, inst_uuids):
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)
2098 def GetMultiInstanceInfoByName(self, inst_names):
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)
2119 def GetAllInstancesInfo(self):
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
2129 def _UnlockedGetAllInstancesInfo(self):
2130 my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
2131 for inst_uuid in self._UnlockedGetInstanceList()])
2132 return my_dict
2133
2134 @ConfigSync(shared=1)
2135 def GetInstancesInfoByFilter(self, filter_fn):
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)
2151 def GetInstanceInfoByName(self, inst_name):
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
2162 def _UnlockedGetInstanceInfoByName(self, inst_name):
2163 for inst in self._UnlockedGetAllInstancesInfo().values():
2164 if inst.name == inst_name:
2165 return inst
2166 return None
2167
2168 def _UnlockedGetInstanceName(self, inst_uuid):
2169 inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
2170 if inst_info is None:
2171 raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
2172 return inst_info.name
2173
2174 @ConfigSync(shared=1)
2175 def GetInstanceName(self, inst_uuid):
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)
2187 def GetInstanceNames(self, inst_uuids):
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()
2199 def SetInstancePrimaryNode(self, inst_uuid, target_node_uuid):
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()
2211 def SetDiskNodes(self, disk_uuid, nodes):
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()
2223 def SetDiskLogicalID(self, disk_uuid, logical_id):
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
2244 def _UnlockedGetInstanceNames(self, inst_uuids):
2245 return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
2246
2247 def _UnlockedAddNode(self, node, ec_id):
2248 """Add a node to the configuration.
2249
2250 @type node: L{objects.Node}
2251 @param node: a Node instance
2252
2253 """
2254 logging.info("Adding node %s to configuration", node.name)
2255
2256 self._EnsureUUID(node, ec_id)
2257
2258 node.serial_no = 1
2259 node.ctime = node.mtime = time.time()
2260 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2261 assert node.uuid in self._ConfigData().nodegroups[node.group].members
2262 self._ConfigData().nodes[node.uuid] = node
2263 self._ConfigData().cluster.serial_no += 1
2264
2265 @ConfigSync()
2266 def AddNode(self, node, ec_id):
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()
2276 def RemoveNode(self, node_uuid):
2277 """Remove a node from the configuration.
2278
2279 """
2280 logging.info("Removing node %s from configuration", node_uuid)
2281
2282 if node_uuid not in self._ConfigData().nodes:
2283 raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
2284
2285 self._UnlockedRemoveNodeFromGroup(self._ConfigData().nodes[node_uuid])
2286 del self._ConfigData().nodes[node_uuid]
2287 self._ConfigData().cluster.serial_no += 1
2288
2289 def ExpandNodeName(self, short_name):
2290 """Attempt to expand an incomplete node name into a node UUID.
2291
2292 """
2293 # Locking is done in L{ConfigWriter.GetAllNodesInfo}
2294 all_nodes = self.GetAllNodesInfo().values()
2295 expanded_name = _MatchNameComponentIgnoreCase(
2296 short_name, [node.name for node in all_nodes])
2297
2298 if expanded_name is not None:
2299 # there has to be exactly one node with that name
2300 node = (filter(lambda n: n.name == expanded_name, all_nodes)[0])
2301 return (node.uuid, node.name)
2302 else:
2303 return (None, None)
2304
2305 def _UnlockedGetNodeInfo(self, node_uuid):
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)
2323 def GetNodeInfo(self, node_uuid):
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)
2337 def GetNodeInstances(self, node_uuid):
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)
2356 def GetNodeGroupInstances(self, uuid, primary_only=False):
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
2375 def _UnlockedGetHvparamsString(self, hvname):
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)
2389 def GetHvparamsString(self, hvname):
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
2401 def _UnlockedGetNodeList(self):
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)
2413 def GetNodeList(self):
2414 """Return the list of nodes which are in the configuration.
2415
2416 """
2417 return self._UnlockedGetNodeList()
2418
2419 def _UnlockedGetOnlineNodeList(self):
2420 """Return the list of nodes which are online.
2421
2422 """
2423 all_nodes = [self._UnlockedGetNodeInfo(node)
2424 for node in self._UnlockedGetNodeList()]
2425 return [node.uuid for node in all_nodes if not node.offline]
2426
2427 @ConfigSync(shared=1)
2428 def GetOnlineNodeList(self):
2429 """Return the list of nodes which are online.
2430
2431 """
2432 return self._UnlockedGetOnlineNodeList()
2433
2434 @ConfigSync(shared=1)
2435 def GetVmCapableNodeList(self):
2436 """Return the list of nodes which are not vm capable.
2437
2438 """
2439 all_nodes = [self._UnlockedGetNodeInfo(node)
2440 for node in self._UnlockedGetNodeList()]
2441 return [node.uuid for node in all_nodes if node.vm_capable]
2442
2443 @ConfigSync(shared=1)
2444 def GetNonVmCapableNodeList(self):
2445 """Return the list of nodes' uuids which are not vm capable.
2446
2447 """
2448 all_nodes = [self._UnlockedGetNodeInfo(node)
2449 for node in self._UnlockedGetNodeList()]
2450 return [node.uuid for node in all_nodes if not node.vm_capable]
2451
2452 @ConfigSync(shared=1)
2453 def GetNonVmCapableNodeNameList(self):
2454 """Return the list of nodes' names which are not vm capable.
2455
2456 """
2457 all_nodes = [self._UnlockedGetNodeInfo(node)
2458 for node in self._UnlockedGetNodeList()]
2459 return [node.name for node in all_nodes if not node.vm_capable]
2460
2461 @ConfigSync(shared=1)
2462 def GetMultiNodeInfo(self, node_uuids):
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
2474 def _UnlockedGetAllNodesInfo(self):
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)
2484 def GetAllNodesInfo(self):
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
2494 def _UnlockedGetNodeInfoByName(self, node_name):
2495 for node in self._UnlockedGetAllNodesInfo().values():
2496 if node.name == node_name:
2497 return node
2498 return None
2499
2500 @ConfigSync(shared=1)
2501 def GetNodeInfoByName(self, node_name):
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)
2513 def GetNodeGroupInfoByName(self, nodegroup_name):
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
2527 def _UnlockedGetNodeName(self, node_spec):
2528 if isinstance(node_spec, objects.Node):
2529 return node_spec.name
2530 elif isinstance(node_spec, basestring):
2531 node_info = self._UnlockedGetNodeInfo(node_spec)
2532 if node_info is None:
2533 raise errors.OpExecError("Unknown node: %s" % node_spec)
2534 return node_info.name
2535 else:
2536 raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2537
2538 @ConfigSync(shared=1)
2539 def GetNodeName(self, node_spec):
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
2550 def _UnlockedGetNodeNames(self, node_specs):
2551 return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2552
2553 @ConfigSync(shared=1)
2554 def GetNodeNames(self, node_specs):
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)
2566 def GetNodeGroupsFromNodes(self, node_uuids):
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
2577 def _UnlockedGetMasterCandidateUuids(self):
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)
2588 def GetMasterCandidateUuids(self):
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
2597 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
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)
2618 def GetMasterCandidateStats(self, exceptions=None):
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 # this should not happen
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
2667 def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2668 """Add a given node to the specified group.
2669
2670 """
2671 if nodegroup_uuid not in self._ConfigData().nodegroups:
2672 # This can happen if a node group gets deleted between its lookup and
2673 # when we're adding the first node to it, since we don't keep a lock in
2674 # the meantime. It's ok though, as we'll fail cleanly if the node group
2675 # is not found anymore.
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
2680 def _UnlockedRemoveNodeFromGroup(self, node):
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()
2696 def AssignGroupNodes(self, mods):
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 # Try to resolve UUIDs first
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 # Node is being assigned to its current group
2717 logging.debug("Node '%s' was assigned to its current group (%s)",
2718 node_uuid, node.group)
2719 continue
2720
2721 # Try to find current group of node
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 # Try to find new group for node
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 # Apply changes
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 # Update members of involved groups
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 # Update timestamps and serials (only once per node/group object)
2758 now = time.time()
2759 for obj in frozenset(itertools.chain(*resmod)): # pylint: disable=W0142
2760 obj.serial_no += 1
2761 obj.mtime = now
2762
2763 # Force ssconf update
2764 self._ConfigData().cluster.serial_no += 1
2765
2766 def _BumpSerialNo(self):
2767 """Bump up the serial number of the config.
2768
2769 """
2770 self._ConfigData().serial_no += 1
2771 self._ConfigData().mtime = time.time()
2772
2773 def _AllUUIDObjects(self):
2774 """Returns all objects with uuid attributes.
2775
2776 """
2777 return (self._ConfigData().instances.values() +
2778 self._ConfigData().nodes.values() +
2779 self._ConfigData().nodegroups.values() +
2780 self._ConfigData().networks.values() +
2781 self._ConfigData().disks.values() +
2782 self._AllNICs() +
2783 [self._ConfigData().cluster])
2784
2785 def GetConfigManager(self, shared=False, forcelock=False):
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
2795 def _AddLockCount(self, count):
2796 self._lock_count += count
2797 return self._lock_count
2798
2799 def _LockCount(self):
2800 return self._lock_count
2801
2802 def _OpenConfig(self, shared, force=False):
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 # we already have the lock, do nothing
2814 else:
2815 self._lock_current_shared = shared
2816 if force:
2817 self._lock_forced = True
2818 # Read the configuration data. If offline, read the file directly.
2819 # If online, call WConfd.
2820 if self._offline:
2821 try:
2822 raw_data = utils.ReadFile(self._cfg_file)
2823 data_dict = serializer.Load(raw_data)
2824 # Make sure the configuration has the right version
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 # Upgrade configuration if needed
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 # poll until we acquire the lock
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
2883 def _CloseConfig(self, save):
2884 """Release resources relating the config data.
2885
2886 """
2887 if self._AddLockCount(-1) > 0:
2888 return # we still have the lock, do nothing
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 # TODO: To WConfd
2903 def _UpgradeConfig(self, saveafter=False):
2904 """Run any upgrade steps.
2905
2906 This method performs both in-object upgrades and also update some data
2907 elements that need uniqueness across the whole configuration or interact
2908 with other objects.
2909
2910 @warning: if 'saveafter' is 'True', this function will call
2911 L{_WriteConfig()} so it needs to be called only from a
2912 "safe" place.
2913
2914 """
2915 # Keep a copy of the persistent part of _config_data to check for changes
2916 # Serialization doesn't guarantee order in dictionaries
2917 if saveafter:
2918 oldconf = copy.deepcopy(self._ConfigData().ToDict())
2919 else:
2920 oldconf = None
2921
2922 # In-object upgrades
2923 self._ConfigData().UpgradeConfig()
2924
2925 for item in self._AllUUIDObjects():
2926 if item.uuid is None:
2927 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2928 if not self._ConfigData().nodegroups:
2929 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2930 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2931 members=[])
2932 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2933 for node in self._ConfigData().nodes.values():
2934 if not node.group:
2935 node.group = self._UnlockedLookupNodeGroup(None)
2936 # This is technically *not* an upgrade, but needs to be done both when
2937 # nodegroups are being added, and upon normally loading the config,
2938 # because the members list of a node group is discarded upon
2939 # serializing/deserializing the object.
2940 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2941
2942 if saveafter:
2943 modified = (oldconf != self._ConfigData().ToDict())
2944 else:
2945 modified = True # can't prove it didn't change, but doesn't matter
2946 if modified and saveafter:
2947 self._WriteConfig()
2948 self._UnlockedDropECReservations(_UPGRADE_CONFIG_JID)
2949 else:
2950 if self._offline:
2951 self._UnlockedVerifyConfigAndLog()
2952
2953 def _WriteConfig(self, destination=None, releaselock=False):
2954 """Write the configuration data to persistent storage.
2955
2956 """
2957 if destination is None:
2958 destination = self._cfg_file
2959
2960 # Save the configuration data. If offline, write the file directly.
2961 # If online, call WConfd.
2962 if self._offline:
2963 self._BumpSerialNo()
2964 txt = serializer.DumpJson(
2965 self._ConfigData().ToDict(_with_private=True),
2966 private_encoder=serializer.EncodeWithPrivateFields
2967 )
2968
2969 getents = self._getents()
2970 try:
2971 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2972 close=False, gid=getents.confd_gid, mode=0640)
2973 except errors.LockError:
2974 raise errors.ConfigurationError("The configuration file has been"
2975 " modified since the last write, cannot"
2976 " update")
2977 try:
2978 self._cfg_id = utils.GetFileID(fd=fd)
2979 finally:
2980 os.close(fd)
2981 else:
2982 try:
2983 if releaselock:
2984 res = self._wconfd.WriteConfigAndUnlock(self._GetWConfdContext(),
2985 self._ConfigData().ToDict())
2986 if not res:
2987 logging.warning("WriteConfigAndUnlock indicates we already have"
2988 " released the lock; assuming this was just a retry"
2989 " and the initial call succeeded")
2990 else:
2991 self._wconfd.WriteConfig(self._GetWConfdContext(),
2992 self._ConfigData().ToDict())
2993 except errors.LockError:
2994 raise errors.ConfigurationError("The configuration file has been"
2995 " modified since the last write, cannot"
2996 " update")
2997
2998 self.write_count += 1
2999
3000 def _GetAllHvparamsStrings(self, hypervisors):
3001 """Get the hvparams of all given hypervisors from the config.
3002
3003 @type hypervisors: list of string
3004 @param hypervisors: list of hypervisor names
3005 @rtype: dict of strings
3006 @returns: dictionary mapping the hypervisor name to a string representation
3007 of the hypervisor's hvparams
3008
3009 """
3010 hvparams = {}
3011 for hv in hypervisors:
3012 hvparams[hv] = self._UnlockedGetHvparamsString(hv)
3013 return hvparams
3014
3015 @staticmethod
3016 def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
3017 """Extends the ssconf_values dictionary by hvparams.
3018
3019 @type ssconf_values: dict of strings
3020 @param ssconf_values: dictionary mapping ssconf_keys to strings
3021 representing the content of ssconf files
3022 @type all_hvparams: dict of strings
3023 @param all_hvparams: dictionary mapping hypervisor names to a string
3024 representation of their hvparams
3025 @rtype: same as ssconf_values
3026 @returns: the ssconf_values dictionary extended by hvparams
3027
3028 """
3029 for hv in all_hvparams:
3030 ssconf_key = constants.SS_HVPARAMS_PREF + hv
3031 ssconf_values[ssconf_key] = all_hvparams[hv]
3032 return ssconf_values
3033
3034 def _UnlockedGetSshPortMap(self, node_infos):
3035 node_ports = dict([(node.name,
3036 self._UnlockedGetNdParams(node).get(
3037 constants.ND_SSH_PORT))
3038 for node in node_infos])
3039 return node_ports
3040
3041 def _UnlockedGetSsconfValues(self):
3042 """Return the values needed by ssconf.
3043
3044 @rtype: dict
3045 @return: a dictionary with keys the ssconf names and values their
3046 associated value
3047
3048 """
3049 fn = "\n".join
3050 instance_names = utils.NiceSort(
3051 [inst.name for inst in
3052 self._UnlockedGetAllInstancesInfo().values()])
3053 node_infos = self._UnlockedGetAllNodesInfo().values()
3054 node_names = [node.name for node in node_infos]
3055 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
3056 for ninfo in node_infos]
3057 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
3058 for ninfo in node_infos]
3059 node_vm_capable = ["%s=%s" % (ninfo.name, str(ninfo.vm_capable))
3060 for ninfo in node_infos]
3061
3062 instance_data = fn(instance_names)
3063 off_data = fn(node.name for node in node_infos if node.offline)
3064 on_data = fn(node.name for node in node_infos if not node.offline)
3065 mc_data = fn(node.name for node in node_infos if node.master_candidate)
3066 mc_ips_data = fn(node.primary_ip for node in node_infos
3067 if node.master_candidate)
3068 node_data = fn(node_names)
3069 node_pri_ips_data = fn(node_pri_ips)
3070 node_snd_ips_data = fn(node_snd_ips)
3071 node_vm_capable_data = fn(node_vm_capable)
3072
3073 cluster = self._ConfigData().cluster
3074 cluster_tags = fn(cluster.GetTags())
3075
3076 master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
3077 for mc_uuid, mc_cert
3078 in cluster.candidate_certs.items())
3079
3080 hypervisor_list = fn(cluster.enabled_hypervisors)
3081 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
3082
3083 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
3084
3085 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
3086 self._ConfigData().nodegroups.values()]
3087 nodegroups_data = fn(utils.NiceSort(nodegroups))
3088 networks = ["%s %s" % (net.uuid, net.name) for net in
3089 self._ConfigData().networks.values()]
3090 networks_data = fn(utils.NiceSort(networks))
3091
3092 ssh_ports = fn("%s=%s" % (node_name, port)
3093 for node_name, port
3094 in self._UnlockedGetSshPortMap(node_infos).items())
3095
3096 ssconf_values = {
3097 constants.SS_CLUSTER_NAME: cluster.cluster_name,
3098 constants.SS_CLUSTER_TAGS: cluster_tags,
3099 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
3100 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
3101 constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
3102 constants.SS_MASTER_CANDIDATES: mc_data,
3103 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
3104 constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
3105 constants.SS_MASTER_IP: cluster.master_ip,
3106 constants.SS_MASTER_NETDEV: cluster.master_netdev,
3107 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
3108 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
3109 constants.SS_NODE_LIST: node_data,
3110 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
3111 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
3112 constants.SS_NODE_VM_CAPABLE: node_vm_capable_data,
3113 constants.SS_OFFLINE_NODES: off_data,
3114 constants.SS_ONLINE_NODES: on_data,
3115 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
3116 constants.SS_INSTANCE_LIST: instance_data,
3117 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
3118 constants.SS_HYPERVISOR_LIST: hypervisor_list,
3119 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
3120 constants.SS_UID_POOL: uid_pool,
3121 constants.SS_NODEGROUPS: nodegroups_data,
3122 constants.SS_NETWORKS: networks_data,
3123 constants.SS_ENABLED_USER_SHUTDOWN: str(cluster.enabled_user_shutdown),
3124 constants.SS_SSH_PORTS: ssh_ports,
3125 }
3126 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
3127 all_hvparams)
3128 bad_values = [(k, v) for k, v in ssconf_values.items()
3129 if not isinstance(v, (str, basestring))]
3130 if bad_values:
3131 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
3132 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
3133 " values: %s" % err)
3134 return ssconf_values
3135
3136 @ConfigSync(shared=1)
3137 def GetSsconfValues(self):
3138 """Wrapper using lock around _UnlockedGetSsconf().
3139
3140 """
3141 return self._UnlockedGetSsconfValues()
3142
3143 @ConfigSync(shared=1)
3144 def GetVGName(self):
3145 """Return the volume group name.
3146
3147 """
3148 return self._ConfigData().cluster.volume_group_name
3149
3150 @ConfigSync()
3151 def SetVGName(self, vg_name):
3152 """Set the volume group name.
3153
3154 """
3155 self._ConfigData().cluster.volume_group_name = vg_name
3156 self._ConfigData().cluster.serial_no += 1
3157
3158 @ConfigSync(shared=1)
3159 def GetDRBDHelper(self):
3160 """Return DRBD usermode helper.
3161
3162 """
3163 return self._ConfigData().cluster.drbd_usermode_helper
3164
3165 @ConfigSync()
3166 def SetDRBDHelper(self, drbd_helper):
3167 """Set DRBD usermode helper.
3168
3169 """
3170 self._ConfigData().cluster.drbd_usermode_helper = drbd_helper
3171 self._ConfigData().cluster.serial_no += 1
3172
3173 @ConfigSync(shared=1)
3174 def GetMACPrefix(self):
3175 """Return the mac prefix.
3176
3177 """
3178 return self._ConfigData().cluster.mac_prefix
3179
3180 @ConfigSync(shared=1)
3181 def GetClusterInfo(self):
3182 """Returns information about the cluster
3183
3184 @rtype: L{objects.Cluster}
3185 @return: the cluster object
3186
3187 """
3188 return self._ConfigData().cluster
3189
3190 @ConfigSync(shared=1)
3191 def DisksOfType(self, dev_type):
3192 """Check if in there is at disk of the given type in the configuration.
3193
3194 """
3195 return self._ConfigData().DisksOfType(dev_type)
3196
3197 @ConfigSync(shared=1)
3198 def GetDetachedConfig(self):
3199 """Returns a detached version of a ConfigManager, which represents
3200 a read-only snapshot of the configuration at this particular time.
3201
3202 """
3203 return DetachedConfig(self._ConfigData())
3204
3205 @ConfigSync()
3206 def Update(self, target, feedback_fn, ec_id=None):
3207 """Notify function to be called after updates.
3208
3209 This function must be called when an object (as returned by
3210 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
3211 caller wants the modifications saved to the backing store. Note
3212 that all modified objects will be saved, but the target argument
3213 is the one the caller wants to ensure that it's saved.
3214
3215 @param target: an instance of either L{objects.Cluster},
3216 L{objects.Node} or L{objects.Instance} which is existing in
3217 the cluster
3218 @param feedback_fn: Callable feedback function