6d0a80ea28c47cf6cbbf8ff53ec7c9e84569b565
[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 # TODO: Break up this file into multiple chunks - Wconfd RPC calls, local config
44 # manipulations, grouped by object they operate on (cluster/instance/disk)
45 # pylint: disable=C0302
46 # pylint: disable=R0904
47 # R0904: Too many public methods
48
49 import copy
50 import os
51 import random
52 import logging
53 import time
54 import threading
55 import itertools
56
57 from ganeti.config.temporary_reservations import TemporaryReservationManager
58 from ganeti.config.utils import ConfigSync, ConfigManager
59 from ganeti.config.verify import (VerifyType, VerifyNic, VerifyIpolicy,
60 ValidateConfig)
61
62 from ganeti import errors
63 from ganeti import utils
64 from ganeti import constants
65 import ganeti.wconfd as wc
66 from ganeti import objects
67 from ganeti import serializer
68 from ganeti import uidpool
69 from ganeti import netutils
70 from ganeti import runtime
71 from ganeti import pathutils
72 from ganeti import network
73
74
75 def GetWConfdContext(ec_id, livelock):
76 """Prepare a context for communication with WConfd.
77
78 WConfd needs to know the identity of each caller to properly manage locks and
79 detect job death. This helper function prepares the identity object given a
80 job ID (optional) and a livelock file.
81
82 @type ec_id: int, or None
83 @param ec_id: the job ID or None, if the caller isn't a job
84 @type livelock: L{ganeti.utils.livelock.LiveLock}
85 @param livelock: a livelock object holding the lockfile needed for WConfd
86 @return: the WConfd context
87
88 """
89 if ec_id is None:
90 return (threading.current_thread().getName(),
91 livelock.GetPath(), os.getpid())
92 else:
93 return (ec_id,
94 livelock.GetPath(), os.getpid())
95
96
97 def GetConfig(ec_id, livelock, **kwargs):
98 """A utility function for constructing instances of ConfigWriter.
99
100 It prepares a WConfd context and uses it to create a ConfigWriter instance.
101
102 @type ec_id: int, or None
103 @param ec_id: the job ID or None, if the caller isn't a job
104 @type livelock: L{ganeti.utils.livelock.LiveLock}
105 @param livelock: a livelock object holding the lockfile needed for WConfd
106 @type kwargs: dict
107 @param kwargs: Any additional arguments for the ConfigWriter constructor
108 @rtype: L{ConfigWriter}
109 @return: the ConfigWriter context
110
111 """
112 kwargs['wconfdcontext'] = GetWConfdContext(ec_id, livelock)
113
114 # if the config is to be opened in the accept_foreign mode, we should
115 # also tell the RPC client not to check for the master node
116 accept_foreign = kwargs.get('accept_foreign', False)
117 kwargs['wconfd'] = wc.Client(allow_non_master=accept_foreign)
118
119 return ConfigWriter(**kwargs)
120
121
122 # job id used for resource management at config upgrade time
123 _UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
124
125
126 def _MatchNameComponentIgnoreCase(short_name, names):
127 """Wrapper around L{utils.text.MatchNameComponent}.
128
129 """
130 return utils.MatchNameComponent(short_name, names, case_sensitive=False)
131
132
133 def _CheckInstanceDiskIvNames(disks):
134 """Checks if instance's disks' C{iv_name} attributes are in order.
135
136 @type disks: list of L{objects.Disk}
137 @param disks: List of disks
138 @rtype: list of tuples; (int, string, string)
139 @return: List of wrongly named disks, each tuple contains disk index,
140 expected and actual name
141
142 """
143 result = []
144
145 for (idx, disk) in enumerate(disks):
146 exp_iv_name = "disk/%s" % idx
147 if disk.iv_name != exp_iv_name:
148 result.append((idx, exp_iv_name, disk.iv_name))
149
150 return result
151
152
153 class ConfigWriter(object):
154 """The interface to the cluster configuration.
155
156 WARNING: The class is no longer thread-safe!
157 Each thread must construct a separate instance.
158
159 @ivar _all_rms: a list of all temporary reservation managers
160
161 Currently the class fulfills 3 main functions:
162 1. lock the configuration for access (monitor)
163 2. reload and write the config if necessary (bridge)
164 3. provide convenient access methods to config data (facade)
165
166 """
167 def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
168 accept_foreign=False, wconfdcontext=None, wconfd=None):
169 self.write_count = 0
170 self._config_data = None
171 self._SetConfigData(None)
172 self._offline = offline
173 if cfg_file is None:
174 self._cfg_file = pathutils.CLUSTER_CONF_FILE
175 else:
176 self._cfg_file = cfg_file
177 self._getents = _getents
178 self._temporary_ids = TemporaryReservationManager()
179 self._all_rms = [self._temporary_ids]
180 # Note: in order to prevent errors when resolving our name later,
181 # we compute it here once and reuse it; it's
182 # better to raise an error before starting to modify the config
183 # file than after it was modified
184 self._my_hostname = netutils.Hostname.GetSysName()
185 self._cfg_id = None
186 self._wconfdcontext = wconfdcontext
187 self._wconfd = wconfd
188 self._accept_foreign = accept_foreign
189 self._lock_count = 0
190 self._lock_current_shared = None
191 self._lock_forced = False
192
193 def _ConfigData(self):
194 return self._config_data
195
196 def OutDate(self):
197 self._config_data = None
198
199 def _SetConfigData(self, cfg):
200 self._config_data = cfg
201
202 def _GetWConfdContext(self):
203 return self._wconfdcontext
204
205 # this method needs to be static, so that we can call it on the class
206 @staticmethod
207 def IsCluster():
208 """Check if the cluster is configured.
209
210 """
211 return os.path.exists(pathutils.CLUSTER_CONF_FILE)
212
213 def _UnlockedGetNdParams(self, node):
214 nodegroup = self._UnlockedGetNodeGroup(node.group)
215 return self._ConfigData().cluster.FillND(node, nodegroup)
216
217 @ConfigSync(shared=1)
218 def GetNdParams(self, node):
219 """Get the node params populated with cluster defaults.
220
221 @type node: L{objects.Node}
222 @param node: The node we want to know the params for
223 @return: A dict with the filled in node params
224
225 """
226 return self._UnlockedGetNdParams(node)
227
228 @ConfigSync(shared=1)
229 def GetNdGroupParams(self, nodegroup):
230 """Get the node groups params populated with cluster defaults.
231
232 @type nodegroup: L{objects.NodeGroup}
233 @param nodegroup: The node group we want to know the params for
234 @return: A dict with the filled in node group params
235
236 """
237 return self._UnlockedGetNdGroupParams(nodegroup)
238
239 def _UnlockedGetNdGroupParams(self, group):
240 """Get the ndparams of the group.
241
242 @type group: L{objects.NodeGroup}
243 @param group: The group we want to know the params for
244 @rtype: dict of str to int
245 @return: A dict with the filled in node group params
246
247 """
248 return self._ConfigData().cluster.FillNDGroup(group)
249
250 @ConfigSync(shared=1)
251 def GetGroupSshPorts(self):
252 """Get a map of group UUIDs to SSH ports.
253
254 @rtype: dict of str to int
255 @return: a dict mapping the UUIDs to the SSH ports
256
257 """
258 port_map = {}
259 for uuid, group in self._config_data.nodegroups.items():
260 ndparams = self._UnlockedGetNdGroupParams(group)
261 port = ndparams.get(constants.ND_SSH_PORT)
262 port_map[uuid] = port
263 return port_map
264
265 @ConfigSync(shared=1)
266 def GetInstanceDiskParams(self, instance):
267 """Get the disk params populated with inherit chain.
268
269 @type instance: L{objects.Instance}
270 @param instance: The instance we want to know the params for
271 @return: A dict with the filled in disk params
272
273 """
274 node = self._UnlockedGetNodeInfo(instance.primary_node)
275 nodegroup = self._UnlockedGetNodeGroup(node.group)
276 return self._UnlockedGetGroupDiskParams(nodegroup)
277
278 def _UnlockedGetInstanceDisks(self, inst_uuid):
279 """Return the disks' info for the given instance
280
281 @type inst_uuid: string
282 @param inst_uuid: The UUID of the instance we want to know the disks for
283
284 @rtype: List of L{objects.Disk}
285 @return: A list with all the disks' info
286
287 """
288 instance = self._UnlockedGetInstanceInfo(inst_uuid)
289 if instance is None:
290 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
291
292 return [self._UnlockedGetDiskInfo(disk_uuid)
293 for disk_uuid in instance.disks]
294
295 @ConfigSync(shared=1)
296 def GetInstanceDisks(self, inst_uuid):
297 """Return the disks' info for the given instance
298
299 This is a simple wrapper over L{_UnlockedGetInstanceDisks}.
300
301 """
302 return self._UnlockedGetInstanceDisks(inst_uuid)
303
304 def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False):
305 """Add a disk to the config and attach it to instance."""
306 if not isinstance(disk, objects.Disk):
307 raise errors.ProgrammerError("Invalid type passed to AddInstanceDisk")
308
309 disk.UpgradeConfig()
310 utils.SimpleRetry(True, self._wconfd.AddInstanceDisk, 0.1, 30,
311 args=[inst_uuid, disk.ToDict(), idx, replace])
312 self.OutDate()
313
314 def AttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
315 """Attach an existing disk to an instance."""
316 utils.SimpleRetry(True, self._wconfd.AttachInstanceDisk, 0.1, 30,
317 args=[inst_uuid, disk_uuid, idx])
318 self.OutDate()
319
320 def _UnlockedRemoveDisk(self, disk_uuid):
321 """Remove the disk from the configuration.
322
323 @type disk_uuid: string
324 @param disk_uuid: The UUID of the disk object
325
326 """
327 if disk_uuid not in self._ConfigData().disks:
328 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)
329
330 # Disk must not be attached anywhere
331 for inst in self._ConfigData().instances.values():
332 if disk_uuid in inst.disks:
333 raise errors.ReservationError("Cannot remove disk %s. Disk is"
334 " attached to instance %s"
335 % (disk_uuid, inst.name))
336
337 # Remove disk from config file
338 del self._ConfigData().disks[disk_uuid]
339 self._ConfigData().cluster.serial_no += 1
340
341 def RemoveInstanceDisk(self, inst_uuid, disk_uuid):
342 """Detach a disk from an instance and remove it from the config."""
343 utils.SimpleRetry(True, self._wconfd.RemoveInstanceDisk, 0.1, 30,
344 args=[inst_uuid, disk_uuid])
345 self.OutDate()
346
347 def DetachInstanceDisk(self, inst_uuid, disk_uuid):
348 """Detach a disk from an instance."""
349 utils.SimpleRetry(True, self._wconfd.DetachInstanceDisk, 0.1, 30,
350 args=[inst_uuid, disk_uuid])
351 self.OutDate()
352
353 def _UnlockedGetDiskInfo(self, disk_uuid):
354 """Returns information about a disk.
355
356 It takes the information from the configuration file.
357
358 @param disk_uuid: UUID of the disk
359
360 @rtype: L{objects.Disk}
361 @return: the disk object
362
363 """
364 if disk_uuid not in self._ConfigData().disks:
365 return None
366
367 return self._ConfigData().disks[disk_uuid]
368
369 @ConfigSync(shared=1)
370 def GetDiskInfo(self, disk_uuid):
371 """Returns information about a disk.
372
373 This is a simple wrapper over L{_UnlockedGetDiskInfo}.
374
375 """
376 return self._UnlockedGetDiskInfo(disk_uuid)
377
378 def _UnlockedGetDiskInfoByName(self, disk_name):
379 """Return information about a named disk.
380
381 Return disk information from the configuration file, searching with the
382 name of the disk.
383
384 @param disk_name: Name of the disk
385
386 @rtype: L{objects.Disk}
387 @return: the disk object
388
389 """
390 disk = None
391 count = 0
392 for d in self._ConfigData().disks.itervalues():
393 if d.name == disk_name:
394 count += 1
395 disk = d
396
397 if count > 1:
398 raise errors.ConfigurationError("There are %s disks with this name: %s"
399 % (count, disk_name))
400
401 return disk
402
403 @ConfigSync(shared=1)
404 def GetDiskInfoByName(self, disk_name):
405 """Return information about a named disk.
406
407 This is a simple wrapper over L{_UnlockedGetDiskInfoByName}.
408
409 """
410 return self._UnlockedGetDiskInfoByName(disk_name)
411
412 def _UnlockedGetDiskList(self):
413 """Get the list of disks.
414
415 @return: array of disks, ex. ['disk2-uuid', 'disk1-uuid']
416
417 """
418 return self._ConfigData().disks.keys()
419
420 @ConfigSync(shared=1)
421 def GetAllDisksInfo(self):
422 """Get the configuration of all disks.
423
424 This is a simple wrapper over L{_UnlockedGetAllDisksInfo}.
425
426 """
427 return self._UnlockedGetAllDisksInfo()
428
429 def _UnlockedGetAllDisksInfo(self):
430 """Get the configuration of all disks.
431
432 @rtype: dict
433 @return: dict of (disk, disk_info), where disk_info is what
434 would GetDiskInfo return for the node
435
436 """
437 my_dict = dict([(disk_uuid, self._UnlockedGetDiskInfo(disk_uuid))
438 for disk_uuid in self._UnlockedGetDiskList()])
439 return my_dict
440
441 def _AllInstanceNodes(self, inst_uuid):
442 """Compute the set of all disk-related nodes for an instance.
443
444 This abstracts away some work from '_UnlockedGetInstanceNodes'
445 and '_UnlockedGetInstanceSecondaryNodes'.
446
447 @type inst_uuid: string
448 @param inst_uuid: The UUID of the instance we want to get nodes for
449 @rtype: set of strings
450 @return: A set of names for all the nodes of the instance
451
452 """
453 instance = self._UnlockedGetInstanceInfo(inst_uuid)
454 if instance is None:
455 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
456
457 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
458 all_nodes = []
459 for disk in instance_disks:
460 all_nodes.extend(disk.all_nodes)
461 return (set(all_nodes), instance)
462
463 def _UnlockedGetInstanceNodes(self, inst_uuid):
464 """Get all disk-related nodes for an instance.
465
466 For non-DRBD instances, this will contain only the instance's primary node,
467 whereas for DRBD instances, it will contain both the primary and the
468 secondaries.
469
470 @type inst_uuid: string
471 @param inst_uuid: The UUID of the instance we want to get nodes for
472 @rtype: list of strings
473 @return: A list of names for all the nodes of the instance
474
475 """
476 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
477 # ensure that primary node is always the first
478 all_nodes.discard(instance.primary_node)
479 return (instance.primary_node, ) + tuple(all_nodes)
480
481 @ConfigSync(shared=1)
482 def GetInstanceNodes(self, inst_uuid):
483 """Get all disk-related nodes for an instance.
484
485 This is just a wrapper over L{_UnlockedGetInstanceNodes}
486
487 """
488 return self._UnlockedGetInstanceNodes(inst_uuid)
489
490 def _UnlockedGetInstanceSecondaryNodes(self, inst_uuid):
491 """Get the list of secondary nodes.
492
493 @type inst_uuid: string
494 @param inst_uuid: The UUID of the instance we want to get nodes for
495 @rtype: list of strings
496 @return: A tuple of names for all the secondary nodes of the instance
497
498 """
499 (all_nodes, instance) = self._AllInstanceNodes(inst_uuid)
500 all_nodes.discard(instance.primary_node)
501 return tuple(all_nodes)
502
503 @ConfigSync(shared=1)
504 def GetInstanceSecondaryNodes(self, inst_uuid):
505 """Get the list of secondary nodes.
506
507 This is a simple wrapper over L{_UnlockedGetInstanceSecondaryNodes}.
508
509 """
510 return self._UnlockedGetInstanceSecondaryNodes(inst_uuid)
511
512 def _UnlockedGetInstanceLVsByNode(self, inst_uuid, lvmap=None):
513 """Provide a mapping of node to LVs a given instance owns.
514
515 @type inst_uuid: string
516 @param inst_uuid: The UUID of the instance we want to
517 compute the LVsByNode for
518 @type lvmap: dict
519 @param lvmap: Optional dictionary to receive the
520 'node' : ['lv', ...] data.
521 @rtype: dict or None
522 @return: None if lvmap arg is given, otherwise, a dictionary of
523 the form { 'node_uuid' : ['volume1', 'volume2', ...], ... };
524 volumeN is of the form "vg_name/lv_name", compatible with
525 GetVolumeList()
526
527 """
528 def _MapLVsByNode(lvmap, devices, node_uuid):
529 """Recursive helper function."""
530 if not node_uuid in lvmap:
531 lvmap[node_uuid] = []
532
533 for dev in devices:
534 if dev.dev_type == constants.DT_PLAIN:
535 if not dev.forthcoming:
536 lvmap[node_uuid].append(dev.logical_id[0] + "/" + dev.logical_id[1])
537
538 elif dev.dev_type in constants.DTS_DRBD:
539 if dev.children:
540 _MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
541 _MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
542
543 elif dev.children:
544 _MapLVsByNode(lvmap, dev.children, node_uuid)
545
546 instance = self._UnlockedGetInstanceInfo(inst_uuid)
547 if instance is None:
548 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
549
550 if lvmap is None:
551 lvmap = {}
552 ret = lvmap
553 else:
554 ret = None
555
556 _MapLVsByNode(lvmap,
557 self._UnlockedGetInstanceDisks(instance.uuid),
558 instance.primary_node)
559 return ret
560
561 @ConfigSync(shared=1)
562 def GetInstanceLVsByNode(self, inst_uuid, lvmap=None):
563 """Provide a mapping of node to LVs a given instance owns.
564
565 This is a simple wrapper over L{_UnlockedGetInstanceLVsByNode}
566
567 """
568 return self._UnlockedGetInstanceLVsByNode(inst_uuid, lvmap=lvmap)
569
570 @ConfigSync(shared=1)
571 def GetGroupDiskParams(self, group):
572 """Get the disk params populated with inherit chain.
573
574 @type group: L{objects.NodeGroup}
575 @param group: The group we want to know the params for
576 @return: A dict with the filled in disk params
577
578 """
579 return self._UnlockedGetGroupDiskParams(group)
580
581 def _UnlockedGetGroupDiskParams(self, group):
582 """Get the disk params populated with inherit chain down to node-group.
583
584 @type group: L{objects.NodeGroup}
585 @param group: The group we want to know the params for
586 @return: A dict with the filled in disk params
587
588 """
589 data = self._ConfigData().cluster.SimpleFillDP(group.diskparams)
590 assert isinstance(data, dict), "Not a dictionary: " + str(data)
591 return data
592
593 @ConfigSync(shared=1)
594 def GetPotentialMasterCandidates(self):
595 """Gets the list of node names of potential master candidates.
596
597 @rtype: list of str
598 @return: list of node names of potential master candidates
599
600 """
601 # FIXME: Note that currently potential master candidates are nodes
602 # but this definition will be extended once RAPI-unmodifiable
603 # parameters are introduced.
604 nodes = self._UnlockedGetAllNodesInfo()
605 return [node_info.name for node_info in nodes.values()]
606
607 def GenerateMAC(self, net_uuid, _ec_id):
608 """Generate a MAC for an instance.
609
610 This should check the current instances for duplicates.
611
612 """
613 return self._wconfd.GenerateMAC(self._GetWConfdContext(), net_uuid)
614
615 def ReserveMAC(self, mac, _ec_id):
616 """Reserve a MAC for an instance.
617
618 This only checks instances managed by this cluster, it does not
619 check for potential collisions elsewhere.
620
621 """
622 self._wconfd.ReserveMAC(self._GetWConfdContext(), mac)
623
624 @ConfigSync(shared=1)
625 def CommitTemporaryIps(self, _ec_id):
626 """Tell WConfD to commit all temporary ids"""
627 self._wconfd.CommitTemporaryIps(self._GetWConfdContext())
628
629 def ReleaseIp(self, net_uuid, address, _ec_id):
630 """Give a specific IP address back to an IP pool.
631
632 The IP address is returned to the IP pool and marked as reserved.
633
634 """
635 if net_uuid:
636 if self._offline:
637 raise errors.ProgrammerError("Can't call ReleaseIp in offline mode")
638 self._wconfd.ReleaseIp(self._GetWConfdContext(), net_uuid, address)
639
640 def GenerateIp(self, net_uuid, _ec_id):
641 """Find a free IPv4 address for an instance.
642
643 """
644 if self._offline:
645 raise errors.ProgrammerError("Can't call GenerateIp in offline mode")
646 return self._wconfd.GenerateIp(self._GetWConfdContext(), net_uuid)
647
648 def ReserveIp(self, net_uuid, address, _ec_id, check=True):
649 """Reserve a given IPv4 address for use by an instance.
650
651 """
652 if self._offline:
653 raise errors.ProgrammerError("Can't call ReserveIp in offline mode")
654 return self._wconfd.ReserveIp(self._GetWConfdContext(), net_uuid, address,
655 check)
656
657 def ReserveLV(self, lv_name, _ec_id):
658 """Reserve an VG/LV pair for an instance.
659
660 @type lv_name: string
661 @param lv_name: the logical volume name to reserve
662
663 """
664 return self._wconfd.ReserveLV(self._GetWConfdContext(), lv_name)
665
666 def GenerateDRBDSecret(self, _ec_id):
667 """Generate a DRBD secret.
668
669 This checks the current disks for duplicates.
670
671 """
672 return self._wconfd.GenerateDRBDSecret(self._GetWConfdContext())
673
674 # FIXME: After _AllIDs is removed, move it to config_mock.py
675 def _AllLVs(self):
676 """Compute the list of all LVs.
677
678 """
679 lvnames = set()
680 for instance in self._ConfigData().instances.values():
681 node_data = self._UnlockedGetInstanceLVsByNode(instance.uuid)
682 for lv_list in node_data.values():
683 lvnames.update(lv_list)
684 return lvnames
685
686 def _AllNICs(self):
687 """Compute the list of all NICs.
688
689 """
690 nics = []
691 for instance in self._ConfigData().instances.values():
692 nics.extend(instance.nics)
693 return nics
694
695 def _AllIDs(self, include_temporary):
696 """Compute the list of all UUIDs and names we have.
697
698 @type include_temporary: boolean
699 @param include_temporary: whether to include the _temporary_ids set
700 @rtype: set
701 @return: a set of IDs
702
703 """
704 existing = set()
705 if include_temporary:
706 existing.update(self._temporary_ids.GetReserved())
707 existing.update(self._AllLVs())
708 existing.update(self._ConfigData().instances.keys())
709 existing.update(self._ConfigData().nodes.keys())
710 existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
711 return existing
712
713 def _GenerateUniqueID(self, ec_id):
714 """Generate an unique UUID.
715
716 This checks the current node, instances and disk names for
717 duplicates.
718
719 @rtype: string
720 @return: the unique id
721
722 """
723 existing = self._AllIDs(include_temporary=False)
724 return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
725
726 @ConfigSync(shared=1)
727 def GenerateUniqueID(self, ec_id):
728 """Generate an unique ID.
729
730 This is just a wrapper over the unlocked version.
731
732 @type ec_id: string
733 @param ec_id: unique id for the job to reserve the id to
734
735 """
736 return self._GenerateUniqueID(ec_id)
737
738 def _AllMACs(self):
739 """Return all MACs present in the config.
740
741 @rtype: list
742 @return: the list of all MACs
743
744 """
745 result = []
746 for instance in self._ConfigData().instances.values():
747 for nic in instance.nics:
748 result.append(nic.mac)
749
750 return result
751
752 def _AllDRBDSecrets(self):
753 """Return all DRBD secrets present in the config.
754
755 @rtype: list
756 @return: the list of all DRBD secrets
757
758 """
759 def helper(disk, result):
760 """Recursively gather secrets from this disk."""
761 if disk.dev_type == constants.DT_DRBD8:
762 result.append(disk.logical_id[5])
763 if disk.children:
764 for child in disk.children:
765 helper(child, result)
766
767 result = []
768 for disk in self._ConfigData().disks.values():
769 helper(disk, result)
770
771 return result
772
773 @staticmethod
774 def _VerifyDisks(data, result):
775 """Per-disk verification checks
776
777 Extends L{result} with diagnostic information about the disks.
778
779 @type data: see L{_ConfigData}
780 @param data: configuration data
781
782 @type result: list of strings
783 @param result: list containing diagnostic messages
784
785 """
786 for disk_uuid in data.disks:
787 disk = data.disks[disk_uuid]
788 result.extend(["disk %s error: %s" % (disk.uuid, msg)
789 for msg in disk.Verify()])
790 if disk.uuid != disk_uuid:
791 result.append("disk '%s' is indexed by wrong UUID '%s'" %
792 (disk.name, disk_uuid))
793
794 def _UnlockedVerifyConfig(self):
795 """Verify function.
796
797 @rtype: list
798 @return: a list of error messages; a non-empty list signifies
799 configuration errors
800
801 """
802 # pylint: disable=R0914
803 result = []
804 seen_macs = []
805 ports = {}
806 data = self._ConfigData()
807 cluster = data.cluster
808
809 # First call WConfd to perform its checks, if we're not offline
810 if not self._offline:
811 try:
812 self._wconfd.VerifyConfig()
813 except errors.ConfigVerifyError, err:
814 try:
815 for msg in err.args[1]:
816 result.append(msg)
817 except IndexError:
818 pass
819
820 # check cluster parameters
821 VerifyType("cluster", "beparams", cluster.SimpleFillBE({}),
822 constants.BES_PARAMETER_TYPES, result.append)
823 VerifyType("cluster", "nicparams", cluster.SimpleFillNIC({}),
824 constants.NICS_PARAMETER_TYPES, result.append)
825 VerifyNic("cluster", cluster.SimpleFillNIC({}), result.append)
826 VerifyType("cluster", "ndparams", cluster.SimpleFillND({}),
827 constants.NDS_PARAMETER_TYPES, result.append)
828 VerifyIpolicy("cluster", cluster.ipolicy, True, result.append)
829
830 for disk_template in cluster.diskparams:
831 if disk_template not in constants.DTS_HAVE_ACCESS:
832 continue
833
834 access = cluster.diskparams[disk_template].get(constants.LDP_ACCESS,
835 constants.DISK_KERNELSPACE)
836 if access not in constants.DISK_VALID_ACCESS_MODES:
837 result.append(
838 "Invalid value of '%s:%s': '%s' (expected one of %s)" % (
839 disk_template, constants.LDP_ACCESS, access,
840 utils.CommaJoin(constants.DISK_VALID_ACCESS_MODES)
841 )
842 )
843
844 self._VerifyDisks(data, result)
845
846 # per-instance checks
847 for instance_uuid in data.instances:
848 instance = data.instances[instance_uuid]
849 if instance.uuid != instance_uuid:
850 result.append("instance '%s' is indexed by wrong UUID '%s'" %
851 (instance.name, instance_uuid))
852 if instance.primary_node not in data.nodes:
853 result.append("instance '%s' has invalid primary node '%s'" %
854 (instance.name, instance.primary_node))
855 for snode in self._UnlockedGetInstanceSecondaryNodes(instance.uuid):
856 if snode not in data.nodes:
857 result.append("instance '%s' has invalid secondary node '%s'" %
858 (instance.name, snode))
859 for idx, nic in enumerate(instance.nics):
860 if nic.mac in seen_macs:
861 result.append("instance '%s' has NIC %d mac %s duplicate" %
862 (instance.name, idx, nic.mac))
863 else:
864 seen_macs.append(nic.mac)
865 if nic.nicparams:
866 filled = cluster.SimpleFillNIC(nic.nicparams)
867 owner = "instance %s nic %d" % (instance.name, idx)
868 VerifyType(owner, "nicparams",
869 filled, constants.NICS_PARAMETER_TYPES, result.append)
870 VerifyNic(owner, filled, result.append)
871
872 # parameter checks
873 if instance.beparams:
874 VerifyType("instance %s" % instance.name, "beparams",
875 cluster.FillBE(instance), constants.BES_PARAMETER_TYPES,
876 result.append)
877
878 # check that disks exists
879 for disk_uuid in instance.disks:
880 if disk_uuid not in data.disks:
881 result.append("Instance '%s' has invalid disk '%s'" %
882 (instance.name, disk_uuid))
883
884 instance_disks = self._UnlockedGetInstanceDisks(instance.uuid)
885 # gather the drbd ports for duplicate checks
886 for (idx, dsk) in enumerate(instance_disks):
887 if dsk.dev_type in constants.DTS_DRBD:
888 tcp_port = dsk.logical_id[2]
889 if tcp_port not in ports:
890 ports[tcp_port] = []
891 ports[tcp_port].append((instance.name, "drbd disk %s" % idx))
892 # gather network port reservation
893 net_port = getattr(instance, "network_port", None)
894 if net_port is not None:
895 if net_port not in ports:
896 ports[net_port] = []
897 ports[net_port].append((instance.name, "network port"))
898
899 wrong_names = _CheckInstanceDiskIvNames(instance_disks)
900 if wrong_names:
901 tmp = "; ".join(("name of disk %s should be '%s', but is '%s'" %
902 (idx, exp_name, actual_name))
903 for (idx, exp_name, actual_name) in wrong_names)
904
905 result.append("Instance '%s' has wrongly named disks: %s" %
906 (instance.name, tmp))
907
908 # cluster-wide pool of free ports
909 for free_port in cluster.tcpudp_port_pool:
910 if free_port not in ports:
911 ports[free_port] = []
912 ports[free_port].append(("cluster", "port marked as free"))
913
914 # compute tcp/udp duplicate ports
915 keys = ports.keys()
916 keys.sort()
917 for pnum in keys:
918 pdata = ports[pnum]
919 if len(pdata) > 1:
920 txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
921 result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
922
923 # highest used tcp port check
924 if keys:
925 if keys[-1] > cluster.highest_used_port:
926 result.append("Highest used port mismatch, saved %s, computed %s" %
927 (cluster.highest_used_port, keys[-1]))
928
929 if not data.nodes[cluster.master_node].master_candidate:
930 result.append("Master node is not a master candidate")
931
932 # master candidate checks
933 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
934 if mc_now < mc_max:
935 result.append("Not enough master candidates: actual %d, target %d" %
936 (mc_now, mc_max))
937
938 # node checks
939 for node_uuid, node in data.nodes.items():
940 if node.uuid != node_uuid:
941 result.append("Node '%s' is indexed by wrong UUID '%s'" %
942 (node.name, node_uuid))
943 if [node.master_candidate, node.drained, node.offline].count(True) > 1:
944 result.append("Node %s state is invalid: master_candidate=%s,"
945 " drain=%s, offline=%s" %
946 (node.name, node.master_candidate, node.drained,
947 node.offline))
948 if node.group not in data.nodegroups:
949 result.append("Node '%s' has invalid group '%s'" %
950 (node.name, node.group))
951 else:
952 VerifyType("node %s" % node.name, "ndparams",
953 cluster.FillND(node, data.nodegroups[node.group]),
954 constants.NDS_PARAMETER_TYPES, result.append)
955 used_globals = constants.NDC_GLOBALS.intersection(node.ndparams)
956 if used_globals:
957 result.append("Node '%s' has some global parameters set: %s" %
958 (node.name, utils.CommaJoin(used_globals)))
959
960 # nodegroups checks
961 nodegroups_names = set()
962 for nodegroup_uuid in data.nodegroups:
963 nodegroup = data.nodegroups[nodegroup_uuid]
964 if nodegroup.uuid != nodegroup_uuid:
965 result.append("node group '%s' (uuid: '%s') indexed by wrong uuid '%s'"
966 % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
967 if utils.UUID_RE.match(nodegroup.name.lower()):
968 result.append("node group '%s' (uuid: '%s') has uuid-like name" %
969 (nodegroup.name, nodegroup.uuid))
970 if nodegroup.name in nodegroups_names:
971 result.append("duplicate node group name '%s'" % nodegroup.name)
972 else:
973 nodegroups_names.add(nodegroup.name)
974 group_name = "group %s" % nodegroup.name
975 VerifyIpolicy(group_name, cluster.SimpleFillIPolicy(nodegroup.ipolicy),
976 False, result.append)
977 if nodegroup.ndparams:
978 VerifyType(group_name, "ndparams",
979 cluster.SimpleFillND(nodegroup.ndparams),
980 constants.NDS_PARAMETER_TYPES, result.append)
981
982 # drbd minors check
983 # FIXME: The check for DRBD map needs to be implemented in WConfd
984
985 # IP checks
986 default_nicparams = cluster.nicparams[constants.PP_DEFAULT]
987 ips = {}
988
989 def _AddIpAddress(ip, name):
990 ips.setdefault(ip, []).append(name)
991
992 _AddIpAddress(cluster.master_ip, "cluster_ip")
993
994 for node in data.nodes.values():
995 _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
996 if node.secondary_ip != node.primary_ip:
997 _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
998
999 for instance in data.instances.values():
1000 for idx, nic in enumerate(instance.nics):
1001 if nic.ip is None:
1002 continue
1003
1004 nicparams = objects.FillDict(default_nicparams, nic.nicparams)
1005 nic_mode = nicparams[constants.NIC_MODE]
1006 nic_link = nicparams[constants.NIC_LINK]
1007
1008 if nic_mode == constants.NIC_MODE_BRIDGED:
1009 link = "bridge:%s" % nic_link
1010 elif nic_mode == constants.NIC_MODE_ROUTED:
1011 link = "route:%s" % nic_link
1012 elif nic_mode == constants.NIC_MODE_OVS:
1013 link = "ovs:%s" % nic_link
1014 else:
1015 raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
1016
1017 _AddIpAddress("%s/%s/%s" % (link, nic.ip, nic.network),
1018 "instance:%s/nic:%d" % (instance.name, idx))
1019
1020 for ip, owners in ips.items():
1021 if len(owners) > 1:
1022 result.append("IP address %s is used by multiple owners: %s" %
1023 (ip, utils.CommaJoin(owners)))
1024
1025 return result
1026
1027 @ConfigSync(shared=1)
1028 def VerifyConfigAndLog(self, feedback_fn=None):
1029 """A simple wrapper around L{_UnlockedVerifyConfigAndLog}"""
1030 return self._UnlockedVerifyConfigAndLog(feedback_fn=feedback_fn)
1031
1032 def _UnlockedVerifyConfigAndLog(self, feedback_fn=None):
1033 """Verify the configuration and log any errors.
1034
1035 The errors get logged as critical errors and also to the feedback function,
1036 if given.
1037
1038 @param feedback_fn: Callable feedback function
1039 @rtype: list
1040 @return: a list of error messages; a non-empty list signifies
1041 configuration errors
1042
1043 """
1044 assert feedback_fn is None or callable(feedback_fn)
1045
1046 # Warn on config errors, but don't abort the save - the
1047 # configuration has already been modified, and we can't revert;
1048 # the best we can do is to warn the user and save as is, leaving
1049 # recovery to the user
1050 config_errors = self._UnlockedVerifyConfig()
1051 if config_errors:
1052 errmsg = ("Configuration data is not consistent: %s" %
1053 (utils.CommaJoin(config_errors)))
1054 logging.critical(errmsg)
1055 if feedback_fn:
1056 feedback_fn(errmsg)
1057 return config_errors
1058
1059 @ConfigSync(shared=1)
1060 def VerifyConfig(self):
1061 """Verify function.
1062
1063 This is just a wrapper over L{_UnlockedVerifyConfig}.
1064
1065 @rtype: list
1066 @return: a list of error messages; a non-empty list signifies
1067 configuration errors
1068
1069 """
1070 return self._UnlockedVerifyConfig()
1071
1072 def AddTcpUdpPort(self, port):
1073 """Adds a new port to the available port pool."""
1074 utils.SimpleRetry(True, self._wconfd.AddTcpUdpPort, 0.1, 30, args=[port])
1075 self.OutDate()
1076
1077 @ConfigSync(shared=1)
1078 def GetPortList(self):
1079 """Returns a copy of the current port list.
1080
1081 """
1082 return self._ConfigData().cluster.tcpudp_port_pool.copy()
1083
1084 def AllocatePort(self):
1085 """Allocate a port."""
1086 def WithRetry():
1087 port = self._wconfd.AllocatePort()
1088 self.OutDate()
1089
1090 if port is None:
1091 raise utils.RetryAgain()
1092 else:
1093 return port
1094 return utils.Retry(WithRetry, 0.1, 30)
1095
1096 @ConfigSync(shared=1)
1097 def ComputeDRBDMap(self):
1098 """Compute the used DRBD minor/nodes.
1099
1100 This is just a wrapper over a call to WConfd.
1101
1102 @return: dictionary of node_uuid: dict of minor: instance_uuid;
1103 the returned dict will have all the nodes in it (even if with
1104 an empty list).
1105
1106 """
1107 if self._offline:
1108 raise errors.ProgrammerError("Can't call ComputeDRBDMap in offline mode")
1109 else:
1110 return dict((k, dict(v)) for (k, v) in self._wconfd.ComputeDRBDMap())
1111
1112 def AllocateDRBDMinor(self, node_uuids, disk_uuid):
1113 """Allocate a drbd minor.
1114
1115 This is just a wrapper over a call to WConfd.
1116
1117 The free minor will be automatically computed from the existing
1118 devices. A node can not be given multiple times.
1119 The result is the list of minors, in the same
1120 order as the passed nodes.
1121
1122 @type node_uuids: list of strings
1123 @param node_uuids: the nodes in which we allocate minors
1124 @type disk_uuid: string
1125 @param disk_uuid: the disk for which we allocate minors
1126 @rtype: list of ints
1127 @return: A list of minors in the same order as the passed nodes
1128
1129 """
1130 assert isinstance(disk_uuid, basestring), \
1131 "Invalid argument '%s' passed to AllocateDRBDMinor" % disk_uuid
1132
1133 if self._offline:
1134 raise errors.ProgrammerError("Can't call AllocateDRBDMinor"
1135 " in offline mode")
1136
1137 result = self._wconfd.AllocateDRBDMinor(disk_uuid, node_uuids)
1138 logging.debug("Request to allocate drbd minors, input: %s, returning %s",
1139 node_uuids, result)
1140 return result
1141
1142 def ReleaseDRBDMinors(self, disk_uuid):
1143 """Release temporary drbd minors allocated for a given disk.
1144
1145 This is just a wrapper over a call to WConfd.
1146
1147 @type disk_uuid: string
1148 @param disk_uuid: the disk for which temporary minors should be released
1149
1150 """
1151 assert isinstance(disk_uuid, basestring), \
1152 "Invalid argument passed to ReleaseDRBDMinors"
1153 # in offline mode we allow the calls to release DRBD minors,
1154 # because then nothing can be allocated anyway;
1155 # this is useful for testing
1156 if not self._offline:
1157 self._wconfd.ReleaseDRBDMinors(disk_uuid)
1158
1159 @ConfigSync(shared=1)
1160 def GetInstanceDiskTemplate(self, inst_uuid):
1161 """Return the disk template of an instance.
1162
1163 This corresponds to the currently attached disks. If no disks are attached,
1164 it is L{constants.DT_DISKLESS}, if homogeneous disk types are attached,
1165 that type is returned, if that isn't the case, L{constants.DT_MIXED} is
1166 returned.
1167
1168 @type inst_uuid: str
1169 @param inst_uuid: The uuid of the instance.
1170 """
1171 return utils.GetDiskTemplate(self._UnlockedGetInstanceDisks(inst_uuid))
1172
1173 @ConfigSync(shared=1)
1174 def GetConfigVersion(self):
1175 """Get the configuration version.
1176
1177 @return: Config version
1178
1179 """
1180 return self._ConfigData().version
1181
1182 @ConfigSync(shared=1)
1183 def GetClusterName(self):
1184 """Get cluster name.
1185
1186 @return: Cluster name
1187
1188 """
1189 return self._ConfigData().cluster.cluster_name
1190
1191 @ConfigSync(shared=1)
1192 def GetMasterNode(self):
1193 """Get the UUID of the master node for this cluster.
1194
1195 @return: Master node UUID
1196
1197 """
1198 return self._ConfigData().cluster.master_node
1199
1200 @ConfigSync(shared=1)
1201 def GetMasterNodeName(self):
1202 """Get the hostname of the master node for this cluster.
1203
1204 @return: Master node hostname
1205
1206 """
1207 return self._UnlockedGetNodeName(self._ConfigData().cluster.master_node)
1208
1209 @ConfigSync(shared=1)
1210 def GetMasterNodeInfo(self):
1211 """Get the master node information for this cluster.
1212
1213 @rtype: objects.Node
1214 @return: Master node L{objects.Node} object
1215
1216 """
1217 return self._UnlockedGetNodeInfo(self._ConfigData().cluster.master_node)
1218
1219 @ConfigSync(shared=1)
1220 def GetMasterIP(self):
1221 """Get the IP of the master node for this cluster.
1222
1223 @return: Master IP
1224
1225 """
1226 return self._ConfigData().cluster.master_ip
1227
1228 @ConfigSync(shared=1)
1229 def GetMasterNetdev(self):
1230 """Get the master network device for this cluster.
1231
1232 """
1233 return self._ConfigData().cluster.master_netdev
1234
1235 @ConfigSync(shared=1)
1236 def GetMasterNetmask(self):
1237 """Get the netmask of the master node for this cluster.
1238
1239 """
1240 return self._ConfigData().cluster.master_netmask
1241
1242 @ConfigSync(shared=1)
1243 def GetUseExternalMipScript(self):
1244 """Get flag representing whether to use the external master IP setup script.
1245
1246 """
1247 return self._ConfigData().cluster.use_external_mip_script
1248
1249 @ConfigSync(shared=1)
1250 def GetFileStorageDir(self):
1251 """Get the file storage dir for this cluster.
1252
1253 """
1254 return self._ConfigData().cluster.file_storage_dir
1255
1256 @ConfigSync(shared=1)
1257 def GetSharedFileStorageDir(self):
1258 """Get the shared file storage dir for this cluster.
1259
1260 """
1261 return self._ConfigData().cluster.shared_file_storage_dir
1262
1263 @ConfigSync(shared=1)
1264 def GetGlusterStorageDir(self):
1265 """Get the Gluster storage dir for this cluster.
1266
1267 """
1268 return self._ConfigData().cluster.gluster_storage_dir
1269
1270 @ConfigSync(shared=1)
1271 def GetHypervisorType(self):
1272 """Get the hypervisor type for this cluster.
1273
1274 """
1275 return self._ConfigData().cluster.enabled_hypervisors[0]
1276
1277 @ConfigSync(shared=1)
1278 def GetRsaHostKey(self):
1279 """Return the rsa hostkey from the config.
1280
1281 @rtype: string
1282 @return: the rsa hostkey
1283
1284 """
1285 return self._ConfigData().cluster.rsahostkeypub
1286
1287 @ConfigSync(shared=1)
1288 def GetDsaHostKey(self):
1289 """Return the dsa hostkey from the config.
1290
1291 @rtype: string
1292 @return: the dsa hostkey
1293
1294 """
1295 return self._ConfigData().cluster.dsahostkeypub
1296
1297 @ConfigSync(shared=1)
1298 def GetDefaultIAllocator(self):
1299 """Get the default instance allocator for this cluster.
1300
1301 """
1302 return self._ConfigData().cluster.default_iallocator
1303
1304 @ConfigSync(shared=1)
1305 def GetDefaultIAllocatorParameters(self):
1306 """Get the default instance allocator parameters for this cluster.
1307
1308 @rtype: dict
1309 @return: dict of iallocator parameters
1310
1311 """
1312 return self._ConfigData().cluster.default_iallocator_params
1313
1314 @ConfigSync(shared=1)
1315 def GetPrimaryIPFamily(self):
1316 """Get cluster primary ip family.
1317
1318 @return: primary ip family
1319
1320 """
1321 return self._ConfigData().cluster.primary_ip_family
1322
1323 @ConfigSync(shared=1)
1324 def GetMasterNetworkParameters(self):
1325 """Get network parameters of the master node.
1326
1327 @rtype: L{object.MasterNetworkParameters}
1328 @return: network parameters of the master node
1329
1330 """
1331 cluster = self._ConfigData().cluster
1332 result = objects.MasterNetworkParameters(
1333 uuid=cluster.master_node, ip=cluster.master_ip,
1334 netmask=cluster.master_netmask, netdev=cluster.master_netdev,
1335 ip_family=cluster.primary_ip_family)
1336
1337 return result
1338
1339 @ConfigSync(shared=1)
1340 def GetInstallImage(self):
1341 """Get the install image location
1342
1343 @rtype: string
1344 @return: location of the install image
1345
1346 """
1347 return self._ConfigData().cluster.install_image
1348
1349 @ConfigSync()
1350 def SetInstallImage(self, install_image):
1351 """Set the install image location
1352
1353 @type install_image: string
1354 @param install_image: location of the install image
1355
1356 """
1357 self._ConfigData().cluster.install_image = install_image
1358
1359 @ConfigSync(shared=1)
1360 def GetInstanceCommunicationNetwork(self):
1361 """Get cluster instance communication network
1362
1363 @rtype: string
1364 @return: instance communication network, which is the name of the
1365 network used for instance communication
1366
1367 """
1368 return self._ConfigData().cluster.instance_communication_network
1369
1370 @ConfigSync()
1371 def SetInstanceCommunicationNetwork(self, network_name):
1372 """Set cluster instance communication network
1373
1374 @type network_name: string
1375 @param network_name: instance communication network, which is the name of
1376 the network used for instance communication
1377
1378 """
1379 self._ConfigData().cluster.instance_communication_network = network_name
1380
1381 @ConfigSync(shared=1)
1382 def GetZeroingImage(self):
1383 """Get the zeroing image location
1384
1385 @rtype: string
1386 @return: the location of the zeroing image
1387
1388 """
1389 return self._config_data.cluster.zeroing_image
1390
1391 @ConfigSync(shared=1)
1392 def GetCompressionTools(self):
1393 """Get cluster compression tools
1394
1395 @rtype: list of string
1396 @return: a list of tools that are cleared for use in this cluster for the
1397 purpose of compressing data
1398
1399 """
1400 return self._ConfigData().cluster.compression_tools
1401
1402 @ConfigSync()
1403 def SetCompressionTools(self, tools):
1404 """Set cluster compression tools
1405
1406 @type tools: list of string
1407 @param tools: a list of tools that are cleared for use in this cluster for
1408 the purpose of compressing data
1409
1410 """
1411 self._ConfigData().cluster.compression_tools = tools
1412
1413 @ConfigSync()
1414 def AddNodeGroup(self, group, ec_id, check_uuid=True):
1415 """Add a node group to the configuration.
1416
1417 This method calls group.UpgradeConfig() to fill any missing attributes
1418 according to their default values.
1419
1420 @type group: L{objects.NodeGroup}
1421 @param group: the NodeGroup object to add
1422 @type ec_id: string
1423 @param ec_id: unique id for the job to use when creating a missing UUID
1424 @type check_uuid: bool
1425 @param check_uuid: add an UUID to the group if it doesn't have one or, if
1426 it does, ensure that it does not exist in the
1427 configuration already
1428
1429 """
1430 self._UnlockedAddNodeGroup(group, ec_id, check_uuid)
1431
1432 def _UnlockedAddNodeGroup(self, group, ec_id, check_uuid):
1433 """Add a node group to the configuration.
1434
1435 """
1436 logging.info("Adding node group %s to configuration", group.name)
1437
1438 # Some code might need to add a node group with a pre-populated UUID
1439 # generated with ConfigWriter.GenerateUniqueID(). We allow them to bypass
1440 # the "does this UUID" exist already check.
1441 if check_uuid:
1442 self._EnsureUUID(group, ec_id)
1443
1444 try:
1445 existing_uuid = self._UnlockedLookupNodeGroup(group.name)
1446 except errors.OpPrereqError:
1447 pass
1448 else:
1449 raise errors.OpPrereqError("Desired group name '%s' already exists as a"
1450 " node group (UUID: %s)" %
1451 (group.name, existing_uuid),
1452 errors.ECODE_EXISTS)
1453
1454 group.serial_no = 1
1455 group.ctime = group.mtime = time.time()
1456 group.UpgradeConfig()
1457
1458 self._ConfigData().nodegroups[group.uuid] = group
1459 self._ConfigData().cluster.serial_no += 1
1460
1461 @ConfigSync()
1462 def RemoveNodeGroup(self, group_uuid):
1463 """Remove a node group from the configuration.
1464
1465 @type group_uuid: string
1466 @param group_uuid: the UUID of the node group to remove
1467
1468 """
1469 logging.info("Removing node group %s from configuration", group_uuid)
1470
1471 if group_uuid not in self._ConfigData().nodegroups:
1472 raise errors.ConfigurationError("Unknown node group '%s'" % group_uuid)
1473
1474 assert len(self._ConfigData().nodegroups) != 1, \
1475 "Group '%s' is the only group, cannot be removed" % group_uuid
1476
1477 del self._ConfigData().nodegroups[group_uuid]
1478 self._ConfigData().cluster.serial_no += 1
1479
1480 def _UnlockedLookupNodeGroup(self, target):
1481 """Lookup a node group's UUID.
1482
1483 @type target: string or None
1484 @param target: group name or UUID or None to look for the default
1485 @rtype: string
1486 @return: nodegroup UUID
1487 @raises errors.OpPrereqError: when the target group cannot be found
1488
1489 """
1490 if target is None:
1491 if len(self._ConfigData().nodegroups) != 1:
1492 raise errors.OpPrereqError("More than one node group exists. Target"
1493 " group must be specified explicitly.")
1494 else:
1495 return self._ConfigData().nodegroups.keys()[0]
1496 if target in self._ConfigData().nodegroups:
1497 return target
1498 for nodegroup in self._ConfigData().nodegroups.values():
1499 if nodegroup.name == target:
1500 return nodegroup.uuid
1501 raise errors.OpPrereqError("Node group '%s' not found" % target,
1502 errors.ECODE_NOENT)
1503
1504 @ConfigSync(shared=1)
1505 def LookupNodeGroup(self, target):
1506 """Lookup a node group's UUID.
1507
1508 This function is just a wrapper over L{_UnlockedLookupNodeGroup}.
1509
1510 @type target: string or None
1511 @param target: group name or UUID or None to look for the default
1512 @rtype: string
1513 @return: nodegroup UUID
1514
1515 """
1516 return self._UnlockedLookupNodeGroup(target)
1517
1518 def _UnlockedGetNodeGroup(self, uuid):
1519 """Lookup a node group.
1520
1521 @type uuid: string
1522 @param uuid: group UUID
1523 @rtype: L{objects.NodeGroup} or None
1524 @return: nodegroup object, or None if not found
1525
1526 """
1527 if uuid not in self._ConfigData().nodegroups:
1528 return None
1529
1530 return self._ConfigData().nodegroups[uuid]
1531
1532 @ConfigSync(shared=1)
1533 def GetNodeGroup(self, uuid):
1534 """Lookup a node group.
1535
1536 @type uuid: string
1537 @param uuid: group UUID
1538 @rtype: L{objects.NodeGroup} or None
1539 @return: nodegroup object, or None if not found
1540
1541 """
1542 return self._UnlockedGetNodeGroup(uuid)
1543
1544 def _UnlockedGetAllNodeGroupsInfo(self):
1545 """Get the configuration of all node groups.
1546
1547 """
1548 return dict(self._ConfigData().nodegroups)
1549
1550 @ConfigSync(shared=1)
1551 def GetAllNodeGroupsInfo(self):
1552 """Get the configuration of all node groups.
1553
1554 """
1555 return self._UnlockedGetAllNodeGroupsInfo()
1556
1557 @ConfigSync(shared=1)
1558 def GetAllNodeGroupsInfoDict(self):
1559 """Get the configuration of all node groups expressed as a dictionary of
1560 dictionaries.
1561
1562 """
1563 return dict((uuid, ng.ToDict()) for (uuid, ng) in
1564 self._UnlockedGetAllNodeGroupsInfo().items())
1565
1566 @ConfigSync(shared=1)
1567 def GetNodeGroupList(self):
1568 """Get a list of node groups.
1569
1570 """
1571 return self._ConfigData().nodegroups.keys()
1572
1573 @ConfigSync(shared=1)
1574 def GetNodeGroupMembersByNodes(self, nodes):
1575 """Get nodes which are member in the same nodegroups as the given nodes.
1576
1577 """
1578 ngfn = lambda node_uuid: self._UnlockedGetNodeInfo(node_uuid).group
1579 return frozenset(member_uuid
1580 for node_uuid in nodes
1581 for member_uuid in
1582 self._UnlockedGetNodeGroup(ngfn(node_uuid)).members)
1583
1584 @ConfigSync(shared=1)
1585 def GetMultiNodeGroupInfo(self, group_uuids):
1586 """Get the configuration of multiple node groups.
1587
1588 @param group_uuids: List of node group UUIDs
1589 @rtype: list
1590 @return: List of tuples of (group_uuid, group_info)
1591
1592 """
1593 return [(uuid, self._UnlockedGetNodeGroup(uuid)) for uuid in group_uuids]
1594
1595 def AddInstance(self, instance, _ec_id, replace=False):
1596 """Add an instance to the config.
1597
1598 This should be used after creating a new instance.
1599
1600 @type instance: L{objects.Instance}
1601 @param instance: the instance object
1602 @type replace: bool
1603 @param replace: if true, expect the instance to be present and
1604 replace rather than add.
1605
1606 """
1607 if not isinstance(instance, objects.Instance):
1608 raise errors.ProgrammerError("Invalid type passed to AddInstance")
1609
1610 instance.serial_no = 1
1611
1612 utils.SimpleRetry(True, self._wconfd.AddInstance, 0.1, 30,
1613 args=[instance.ToDict(),
1614 self._GetWConfdContext(),
1615 replace])
1616 self.OutDate()
1617
1618 def _EnsureUUID(self, item, ec_id):
1619 """Ensures a given object has a valid UUID.
1620
1621 @param item: the instance or node to be checked
1622 @param ec_id: the execution context id for the uuid reservation
1623
1624 """
1625 if not item.uuid:
1626 item.uuid = self._GenerateUniqueID(ec_id)
1627 else:
1628 self._CheckUniqueUUID(item, include_temporary=True)
1629
1630 def _CheckUniqueUUID(self, item, include_temporary):
1631 """Checks that the UUID of the given object is unique.
1632
1633 @param item: the instance or node to be checked
1634 @param include_temporary: whether temporarily generated UUID's should be
1635 included in the check. If the UUID of the item to be checked is
1636 a temporarily generated one, this has to be C{False}.
1637
1638 """
1639 if not item.uuid:
1640 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1641 if item.uuid in self._AllIDs(include_temporary=include_temporary):
1642 raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
1643 " in use" % (item.name, item.uuid))
1644
1645 def _CheckUUIDpresent(self, item):
1646 """Checks that an object with the given UUID exists.
1647
1648 @param item: the instance or other UUID possessing object to verify that
1649 its UUID is present
1650
1651 """
1652 if not item.uuid:
1653 raise errors.ConfigurationError("'%s' must have an UUID" % (item.name,))
1654 if item.uuid not in self._AllIDs(include_temporary=False):
1655 raise errors.ConfigurationError("Cannot replace '%s': UUID %s not present"
1656 % (item.name, item.uuid))
1657
1658 def _SetInstanceStatus(self, inst_uuid, status, disks_active,
1659 admin_state_source):
1660 """Set the instance's status to a given value.
1661
1662 @rtype: L{objects.Instance}
1663 @return: the updated instance object
1664
1665 """
1666 def WithRetry():
1667 result = self._wconfd.SetInstanceStatus(inst_uuid, status,
1668 disks_active, admin_state_source)
1669 self.OutDate()
1670
1671 if result is None:
1672 raise utils.RetryAgain()
1673 else:
1674 return result
1675 return objects.Instance.FromDict(utils.Retry(WithRetry, 0.1, 30))
1676
1677 def MarkInstanceUp(self, inst_uuid):
1678 """Mark the instance status to up in the config.
1679
1680 This also sets the instance disks active flag.
1681
1682 @rtype: L{objects.Instance}
1683 @return: the updated instance object
1684
1685 """
1686 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_UP, True,
1687 constants.ADMIN_SOURCE)
1688
1689 def MarkInstanceOffline(self, inst_uuid):
1690 """Mark the instance status to down in the config.
1691
1692 This also clears the instance disks active flag.
1693
1694 @rtype: L{objects.Instance}
1695 @return: the updated instance object
1696
1697 """
1698 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_OFFLINE, False,
1699 constants.ADMIN_SOURCE)
1700
1701 def RemoveInstance(self, inst_uuid):
1702 """Remove the instance from the configuration.
1703
1704 """
1705 utils.SimpleRetry(True, self._wconfd.RemoveInstance, 0.1, 30,
1706 args=[inst_uuid])
1707 self.OutDate()
1708
1709 @ConfigSync()
1710 def RenameInstance(self, inst_uuid, new_name):
1711 """Rename an instance.
1712
1713 This needs to be done in ConfigWriter and not by RemoveInstance
1714 combined with AddInstance as only we can guarantee an atomic
1715 rename.
1716
1717 """
1718 if inst_uuid not in self._ConfigData().instances:
1719 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1720
1721 inst = self._ConfigData().instances[inst_uuid]
1722 inst.name = new_name
1723
1724 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
1725 for (_, disk) in enumerate(instance_disks):
1726 if disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]:
1727 # rename the file paths in logical and physical id
1728 file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1729 disk.logical_id = (disk.logical_id[0],
1730 utils.PathJoin(file_storage_dir, inst.name,
1731 os.path.basename(disk.logical_id[1])))
1732
1733 # Force update of ssconf files
1734 self._ConfigData().cluster.serial_no += 1
1735
1736 def MarkInstanceDown(self, inst_uuid):
1737 """Mark the status of an instance to down in the configuration.
1738
1739 This does not touch the instance disks active flag, as shut down instances
1740 can still have active disks.
1741
1742 @rtype: L{objects.Instance}
1743 @return: the updated instance object
1744
1745 """
1746 return self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None,
1747 constants.ADMIN_SOURCE)
1748
1749 def MarkInstanceUserDown(self, inst_uuid):
1750 """Mark the status of an instance to user down in the configuration.
1751
1752 This does not touch the instance disks active flag, as user shut
1753 down instances can still have active disks.
1754
1755 """
1756
1757 self._SetInstanceStatus(inst_uuid, constants.ADMINST_DOWN, None,
1758 constants.USER_SOURCE)
1759
1760 def MarkInstanceDisksActive(self, inst_uuid):
1761 """Mark the status of instance disks active.
1762
1763 @rtype: L{objects.Instance}
1764 @return: the updated instance object
1765
1766 """
1767 return self._SetInstanceStatus(inst_uuid, None, True, None)
1768
1769 def MarkInstanceDisksInactive(self, inst_uuid):
1770 """Mark the status of instance disks inactive.
1771
1772 @rtype: L{objects.Instance}
1773 @return: the updated instance object
1774
1775 """
1776 return self._SetInstanceStatus(inst_uuid, None, False, None)
1777
1778 def _UnlockedGetInstanceList(self):
1779 """Get the list of instances.
1780
1781 This function is for internal use, when the config lock is already held.
1782
1783 """
1784 return self._ConfigData().instances.keys()
1785
1786 @ConfigSync(shared=1)
1787 def GetInstanceList(self):
1788 """Get the list of instances.
1789
1790 @return: array of instances, ex. ['instance2-uuid', 'instance1-uuid']
1791
1792 """
1793 return self._UnlockedGetInstanceList()
1794
1795 def ExpandInstanceName(self, short_name):
1796 """Attempt to expand an incomplete instance name.
1797
1798 """
1799 # Locking is done in L{ConfigWriter.GetAllInstancesInfo}
1800 all_insts = self.GetAllInstancesInfo().values()
1801 expanded_name = _MatchNameComponentIgnoreCase(
1802 short_name, [inst.name for inst in all_insts])
1803
1804 if expanded_name is not None:
1805 # there has to be exactly one instance with that name
1806 inst = [n for n in all_insts if n.name == expanded_name][0]
1807 return (inst.uuid, inst.name)
1808 else:
1809 return (None, None)
1810
1811 def _UnlockedGetInstanceInfo(self, inst_uuid):
1812 """Returns information about an instance.
1813
1814 This function is for internal use, when the config lock is already held.
1815
1816 """
1817 if inst_uuid not in self._ConfigData().instances:
1818 return None
1819
1820 return self._ConfigData().instances[inst_uuid]
1821
1822 @ConfigSync(shared=1)
1823 def GetInstanceInfo(self, inst_uuid):
1824 """Returns information about an instance.
1825
1826 It takes the information from the configuration file. Other information of
1827 an instance are taken from the live systems.
1828
1829 @param inst_uuid: UUID of the instance
1830
1831 @rtype: L{objects.Instance}
1832 @return: the instance object
1833
1834 """
1835 return self._UnlockedGetInstanceInfo(inst_uuid)
1836
1837 @ConfigSync(shared=1)
1838 def GetInstanceNodeGroups(self, inst_uuid, primary_only=False):
1839 """Returns set of node group UUIDs for instance's nodes.
1840
1841 @rtype: frozenset
1842
1843 """
1844 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1845 if not instance:
1846 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1847
1848 if primary_only:
1849 nodes = [instance.primary_node]
1850 else:
1851 nodes = self._UnlockedGetInstanceNodes(instance.uuid)
1852
1853 return frozenset(self._UnlockedGetNodeInfo(node_uuid).group
1854 for node_uuid in nodes)
1855
1856 @ConfigSync(shared=1)
1857 def GetInstanceNetworks(self, inst_uuid):
1858 """Returns set of network UUIDs for instance's nics.
1859
1860 @rtype: frozenset
1861
1862 """
1863 instance = self._UnlockedGetInstanceInfo(inst_uuid)
1864 if not instance:
1865 raise errors.ConfigurationError("Unknown instance '%s'" % inst_uuid)
1866
1867 networks = set()
1868 for nic in instance.nics:
1869 if nic.network:
1870 networks.add(nic.network)
1871
1872 return frozenset(networks)
1873
1874 @ConfigSync(shared=1)
1875 def GetMultiInstanceInfo(self, inst_uuids):
1876 """Get the configuration of multiple instances.
1877
1878 @param inst_uuids: list of instance UUIDs
1879 @rtype: list
1880 @return: list of tuples (instance UUID, instance_info), where
1881 instance_info is what would GetInstanceInfo return for the
1882 node, while keeping the original order
1883
1884 """
1885 return [(uuid, self._UnlockedGetInstanceInfo(uuid)) for uuid in inst_uuids]
1886
1887 @ConfigSync(shared=1)
1888 def GetMultiInstanceInfoByName(self, inst_names):
1889 """Get the configuration of multiple instances.
1890
1891 @param inst_names: list of instance names
1892 @rtype: list
1893 @return: list of tuples (instance, instance_info), where
1894 instance_info is what would GetInstanceInfo return for the
1895 node, while keeping the original order
1896
1897 """
1898 result = []
1899 for name in inst_names:
1900 instance = self._UnlockedGetInstanceInfoByName(name)
1901 if instance:
1902 result.append((instance.uuid, instance))
1903 else:
1904 raise errors.ConfigurationError("Instance data of instance '%s'"
1905 " not found." % name)
1906 return result
1907
1908 @ConfigSync(shared=1)
1909 def GetAllInstancesInfo(self):
1910 """Get the configuration of all instances.
1911
1912 @rtype: dict
1913 @return: dict of (instance, instance_info), where instance_info is what
1914 would GetInstanceInfo return for the node
1915
1916 """
1917 return self._UnlockedGetAllInstancesInfo()
1918
1919 def _UnlockedGetAllInstancesInfo(self):
1920 my_dict = dict([(inst_uuid, self._UnlockedGetInstanceInfo(inst_uuid))
1921 for inst_uuid in self._UnlockedGetInstanceList()])
1922 return my_dict
1923
1924 @ConfigSync(shared=1)
1925 def GetInstancesInfoByFilter(self, filter_fn):
1926 """Get instance configuration with a filter.
1927
1928 @type filter_fn: callable
1929 @param filter_fn: Filter function receiving instance object as parameter,
1930 returning boolean. Important: this function is called while the
1931 configuration locks is held. It must not do any complex work or call
1932 functions potentially leading to a deadlock. Ideally it doesn't call any
1933 other functions and just compares instance attributes.
1934
1935 """
1936 return dict((uuid, inst)
1937 for (uuid, inst) in self._ConfigData().instances.items()
1938 if filter_fn(inst))
1939
1940 @ConfigSync(shared=1)
1941 def GetInstanceInfoByName(self, inst_name):
1942 """Get the L{objects.Instance} object for a named instance.
1943
1944 @param inst_name: name of the instance to get information for
1945 @type inst_name: string
1946 @return: the corresponding L{objects.Instance} instance or None if no
1947 information is available
1948
1949 """
1950 return self._UnlockedGetInstanceInfoByName(inst_name)
1951
1952 def _UnlockedGetInstanceInfoByName(self, inst_name):
1953 for inst in self._UnlockedGetAllInstancesInfo().values():
1954 if inst.name == inst_name:
1955 return inst
1956 return None
1957
1958 def _UnlockedGetInstanceName(self, inst_uuid):
1959 inst_info = self._UnlockedGetInstanceInfo(inst_uuid)
1960 if inst_info is None:
1961 raise errors.OpExecError("Unknown instance: %s" % inst_uuid)
1962 return inst_info.name
1963
1964 @ConfigSync(shared=1)
1965 def GetInstanceName(self, inst_uuid):
1966 """Gets the instance name for the passed instance.
1967
1968 @param inst_uuid: instance UUID to get name for
1969 @type inst_uuid: string
1970 @rtype: string
1971 @return: instance name
1972
1973 """
1974 return self._UnlockedGetInstanceName(inst_uuid)
1975
1976 @ConfigSync(shared=1)
1977 def GetInstanceNames(self, inst_uuids):
1978 """Gets the instance names for the passed list of nodes.
1979
1980 @param inst_uuids: list of instance UUIDs to get names for
1981 @type inst_uuids: list of strings
1982 @rtype: list of strings
1983 @return: list of instance names
1984
1985 """
1986 return self._UnlockedGetInstanceNames(inst_uuids)
1987
1988 def SetInstancePrimaryNode(self, inst_uuid, target_node_uuid):
1989 """Sets the primary node of an existing instance
1990
1991 @param inst_uuid: instance UUID
1992 @type inst_uuid: string
1993 @param target_node_uuid: the new primary node UUID
1994 @type target_node_uuid: string
1995
1996 """
1997 utils.SimpleRetry(True, self._wconfd.SetInstancePrimaryNode, 0.1, 30,
1998 args=[inst_uuid, target_node_uuid])
1999 self.OutDate()
2000
2001 @ConfigSync()
2002 def SetDiskNodes(self, disk_uuid, nodes):
2003 """Sets the nodes of an existing disk
2004
2005 @param disk_uuid: disk UUID
2006 @type disk_uuid: string
2007 @param nodes: the new nodes for the disk
2008 @type nodes: list of node uuids
2009
2010 """
2011 self._UnlockedGetDiskInfo(disk_uuid).nodes = nodes
2012
2013 @ConfigSync()
2014 def SetDiskLogicalID(self, disk_uuid, logical_id):
2015 """Sets the logical_id of an existing disk
2016
2017 @param disk_uuid: disk UUID
2018 @type disk_uuid: string
2019 @param logical_id: the new logical_id for the disk
2020 @type logical_id: tuple
2021
2022 """
2023 disk = self._UnlockedGetDiskInfo(disk_uuid)
2024 if disk is None:
2025 raise errors.ConfigurationError("Unknown disk UUID '%s'" % disk_uuid)
2026
2027 if len(disk.logical_id) != len(logical_id):
2028 raise errors.ProgrammerError("Logical ID format mismatch\n"
2029 "Existing logical ID: %s\n"
2030 "New logical ID: %s", disk.logical_id,
2031 logical_id)
2032
2033 disk.logical_id = logical_id
2034
2035 def _UnlockedGetInstanceNames(self, inst_uuids):
2036 return [self._UnlockedGetInstanceName(uuid) for uuid in inst_uuids]
2037
2038 def _UnlockedAddNode(self, node, ec_id):
2039 """Add a node to the configuration.
2040
2041 @type node: L{objects.Node}
2042 @param node: a Node instance
2043
2044 """
2045 logging.info("Adding node %s to configuration", node.name)
2046
2047 self._EnsureUUID(node, ec_id)
2048
2049 node.serial_no = 1
2050 node.ctime = node.mtime = time.time()
2051 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2052 assert node.uuid in self._ConfigData().nodegroups[node.group].members
2053 self._ConfigData().nodes[node.uuid] = node
2054 self._ConfigData().cluster.serial_no += 1
2055
2056 @ConfigSync()
2057 def AddNode(self, node, ec_id):
2058 """Add a node to the configuration.
2059
2060 @type node: L{objects.Node}
2061 @param node: a Node instance
2062
2063 """
2064 self._UnlockedAddNode(node, ec_id)
2065
2066 @ConfigSync()
2067 def RemoveNode(self, node_uuid):
2068 """Remove a node from the configuration.
2069
2070 """
2071 logging.info("Removing node %s from configuration", node_uuid)
2072
2073 if node_uuid not in self._ConfigData().nodes:
2074 raise errors.ConfigurationError("Unknown node '%s'" % node_uuid)
2075
2076 self._UnlockedRemoveNodeFromGroup(self._ConfigData().nodes[node_uuid])
2077 del self._ConfigData().nodes[node_uuid]
2078 self._ConfigData().cluster.serial_no += 1
2079
2080 def ExpandNodeName(self, short_name):
2081 """Attempt to expand an incomplete node name into a node UUID.
2082
2083 """
2084 # Locking is done in L{ConfigWriter.GetAllNodesInfo}
2085 all_nodes = self.GetAllNodesInfo().values()
2086 expanded_name = _MatchNameComponentIgnoreCase(
2087 short_name, [node.name for node in all_nodes])
2088
2089 if expanded_name is not None:
2090 # there has to be exactly one node with that name
2091 node = [n for n in all_nodes if n.name == expanded_name][0]
2092 return (node.uuid, node.name)
2093 else:
2094 return (None, None)
2095
2096 def _UnlockedGetNodeInfo(self, node_uuid):
2097 """Get the configuration of a node, as stored in the config.
2098
2099 This function is for internal use, when the config lock is already
2100 held.
2101
2102 @param node_uuid: the node UUID
2103
2104 @rtype: L{objects.Node}
2105 @return: the node object
2106
2107 """
2108 if node_uuid not in self._ConfigData().nodes:
2109 return None
2110
2111 return self._ConfigData().nodes[node_uuid]
2112
2113 @ConfigSync(shared=1)
2114 def GetNodeInfo(self, node_uuid):
2115 """Get the configuration of a node, as stored in the config.
2116
2117 This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
2118
2119 @param node_uuid: the node UUID
2120
2121 @rtype: L{objects.Node}
2122 @return: the node object
2123
2124 """
2125 return self._UnlockedGetNodeInfo(node_uuid)
2126
2127 @ConfigSync(shared=1)
2128 def GetNodeInstances(self, node_uuid):
2129 """Get the instances of a node, as stored in the config.
2130
2131 @param node_uuid: the node UUID
2132
2133 @rtype: (list, list)
2134 @return: a tuple with two lists: the primary and the secondary instances
2135
2136 """
2137 pri = []
2138 sec = []
2139 for inst in self._ConfigData().instances.values():
2140 if inst.primary_node == node_uuid:
2141 pri.append(inst.uuid)
2142 if node_uuid in self._UnlockedGetInstanceSecondaryNodes(inst.uuid):
2143 sec.append(inst.uuid)
2144 return (pri, sec)
2145
2146 @ConfigSync(shared=1)
2147 def GetNodeGroupInstances(self, uuid, primary_only=False):
2148 """Get the instances of a node group.
2149
2150 @param uuid: Node group UUID
2151 @param primary_only: Whether to only consider primary nodes
2152 @rtype: frozenset
2153 @return: List of instance UUIDs in node group
2154
2155 """
2156 if primary_only:
2157 nodes_fn = lambda inst: [inst.primary_node]
2158 else:
2159 nodes_fn = lambda inst: self._UnlockedGetInstanceNodes(inst.uuid)
2160
2161 return frozenset(inst.uuid
2162 for inst in self._ConfigData().instances.values()
2163 for node_uuid in nodes_fn(inst)
2164 if self._UnlockedGetNodeInfo(node_uuid).group == uuid)
2165
2166 def _UnlockedGetHvparamsString(self, hvname):
2167 """Return the string representation of the list of hyervisor parameters of
2168 the given hypervisor.
2169
2170 @see: C{GetHvparams}
2171
2172 """
2173 result = ""
2174 hvparams = self._ConfigData().cluster.hvparams[hvname]
2175 for key in hvparams:
2176 result += "%s=%s\n" % (key, hvparams[key])
2177 return result
2178
2179 @ConfigSync(shared=1)
2180 def GetHvparamsString(self, hvname):
2181 """Return the hypervisor parameters of the given hypervisor.
2182
2183 @type hvname: string
2184 @param hvname: name of a hypervisor
2185 @rtype: string
2186 @return: string containing key-value-pairs, one pair on each line;
2187 format: KEY=VALUE
2188
2189 """
2190 return self._UnlockedGetHvparamsString(hvname)
2191
2192 def _UnlockedGetNodeList(self):
2193 """Return the list of nodes which are in the configuration.
2194
2195 This function is for internal use, when the config lock is already
2196 held.
2197
2198 @rtype: list
2199
2200 """
2201 return self._ConfigData().nodes.keys()
2202
2203 @ConfigSync(shared=1)
2204 def GetNodeList(self):
2205 """Return the list of nodes which are in the configuration.
2206
2207 """
2208 return self._UnlockedGetNodeList()
2209
2210 def _UnlockedGetOnlineNodeList(self):
2211 """Return the list of nodes which are online.
2212
2213 """
2214 all_nodes = [self._UnlockedGetNodeInfo(node)
2215 for node in self._UnlockedGetNodeList()]
2216 return [node.uuid for node in all_nodes if not node.offline]
2217
2218 @ConfigSync(shared=1)
2219 def GetOnlineNodeList(self):
2220 """Return the list of nodes which are online.
2221
2222 """
2223 return self._UnlockedGetOnlineNodeList()
2224
2225 @ConfigSync(shared=1)
2226 def GetVmCapableNodeList(self):
2227 """Return the list of nodes which are not vm capable.
2228
2229 """
2230 all_nodes = [self._UnlockedGetNodeInfo(node)
2231 for node in self._UnlockedGetNodeList()]
2232 return [node.uuid for node in all_nodes if node.vm_capable]
2233
2234 @ConfigSync(shared=1)
2235 def GetNonVmCapableNodeList(self):
2236 """Return the list of nodes' uuids which are not vm capable.
2237
2238 """
2239 all_nodes = [self._UnlockedGetNodeInfo(node)
2240 for node in self._UnlockedGetNodeList()]
2241 return [node.uuid for node in all_nodes if not node.vm_capable]
2242
2243 @ConfigSync(shared=1)
2244 def GetNonVmCapableNodeNameList(self):
2245 """Return the list of nodes' names which are not vm capable.
2246
2247 """
2248 all_nodes = [self._UnlockedGetNodeInfo(node)
2249 for node in self._UnlockedGetNodeList()]
2250 return [node.name for node in all_nodes if not node.vm_capable]
2251
2252 @ConfigSync(shared=1)
2253 def GetMultiNodeInfo(self, node_uuids):
2254 """Get the configuration of multiple nodes.
2255
2256 @param node_uuids: list of node UUIDs
2257 @rtype: list
2258 @return: list of tuples of (node, node_info), where node_info is
2259 what would GetNodeInfo return for the node, in the original
2260 order
2261
2262 """
2263 return [(uuid, self._UnlockedGetNodeInfo(uuid)) for uuid in node_uuids]
2264
2265 def _UnlockedGetAllNodesInfo(self):
2266 """Gets configuration of all nodes.
2267
2268 @note: See L{GetAllNodesInfo}
2269
2270 """
2271 return dict([(node_uuid, self._UnlockedGetNodeInfo(node_uuid))
2272 for node_uuid in self._UnlockedGetNodeList()])
2273
2274 @ConfigSync(shared=1)
2275 def GetAllNodesInfo(self):
2276 """Get the configuration of all nodes.
2277
2278 @rtype: dict
2279 @return: dict of (node, node_info), where node_info is what
2280 would GetNodeInfo return for the node
2281
2282 """
2283 return self._UnlockedGetAllNodesInfo()
2284
2285 def _UnlockedGetNodeInfoByName(self, node_name):
2286 for node in self._UnlockedGetAllNodesInfo().values():
2287 if node.name == node_name:
2288 return node
2289 return None
2290
2291 @ConfigSync(shared=1)
2292 def GetNodeInfoByName(self, node_name):
2293 """Get the L{objects.Node} object for a named node.
2294
2295 @param node_name: name of the node to get information for
2296 @type node_name: string
2297 @return: the corresponding L{objects.Node} instance or None if no
2298 information is available
2299
2300 """
2301 return self._UnlockedGetNodeInfoByName(node_name)
2302
2303 @ConfigSync(shared=1)
2304 def GetNodeGroupInfoByName(self, nodegroup_name):
2305 """Get the L{objects.NodeGroup} object for a named node group.
2306
2307 @param nodegroup_name: name of the node group to get information for
2308 @type nodegroup_name: string
2309 @return: the corresponding L{objects.NodeGroup} instance or None if no
2310 information is available
2311
2312 """
2313 for nodegroup in self._UnlockedGetAllNodeGroupsInfo().values():
2314 if nodegroup.name == nodegroup_name:
2315 return nodegroup
2316 return None
2317
2318 def _UnlockedGetNodeName(self, node_spec):
2319 if isinstance(node_spec, objects.Node):
2320 return node_spec.name
2321 elif isinstance(node_spec, basestring):
2322 node_info = self._UnlockedGetNodeInfo(node_spec)
2323 if node_info is None:
2324 raise errors.OpExecError("Unknown node: %s" % node_spec)
2325 return node_info.name
2326 else:
2327 raise errors.ProgrammerError("Can't handle node spec '%s'" % node_spec)
2328
2329 @ConfigSync(shared=1)
2330 def GetNodeName(self, node_spec):
2331 """Gets the node name for the passed node.
2332
2333 @param node_spec: node to get names for
2334 @type node_spec: either node UUID or a L{objects.Node} object
2335 @rtype: string
2336 @return: node name
2337
2338 """
2339 return self._UnlockedGetNodeName(node_spec)
2340
2341 def _UnlockedGetNodeNames(self, node_specs):
2342 return [self._UnlockedGetNodeName(node_spec) for node_spec in node_specs]
2343
2344 @ConfigSync(shared=1)
2345 def GetNodeNames(self, node_specs):
2346 """Gets the node names for the passed list of nodes.
2347
2348 @param node_specs: list of nodes to get names for
2349 @type node_specs: list of either node UUIDs or L{objects.Node} objects
2350 @rtype: list of strings
2351 @return: list of node names
2352
2353 """
2354 return self._UnlockedGetNodeNames(node_specs)
2355
2356 @ConfigSync(shared=1)
2357 def GetNodeGroupsFromNodes(self, node_uuids):
2358 """Returns groups for a list of nodes.
2359
2360 @type node_uuids: list of string
2361 @param node_uuids: List of node UUIDs
2362 @rtype: frozenset
2363
2364 """
2365 return frozenset(self._UnlockedGetNodeInfo(uuid).group
2366 for uuid in node_uuids)
2367
2368 def _UnlockedGetMasterCandidateUuids(self):
2369 """Get the list of UUIDs of master candidates.
2370
2371 @rtype: list of strings
2372 @return: list of UUIDs of all master candidates.
2373
2374 """
2375 return [node.uuid for node in self._ConfigData().nodes.values()
2376 if node.master_candidate]
2377
2378 @ConfigSync(shared=1)
2379 def GetMasterCandidateUuids(self):
2380 """Get the list of UUIDs of master candidates.
2381
2382 @rtype: list of strings
2383 @return: list of UUIDs of all master candidates.
2384
2385 """
2386 return self._UnlockedGetMasterCandidateUuids()
2387
2388 def _UnlockedGetMasterCandidateStats(self, exceptions=None):
2389 """Get the number of current and maximum desired and possible candidates.
2390
2391 @type exceptions: list
2392 @param exceptions: if passed, list of nodes that should be ignored
2393 @rtype: tuple
2394 @return: tuple of (current, desired and possible, possible)
2395
2396 """
2397 mc_now = mc_should = mc_max = 0
2398 for node in self._ConfigData().nodes.values():
2399 if exceptions and node.uuid in exceptions:
2400 continue
2401 if not (node.offline or node.drained) and node.master_capable:
2402 mc_max += 1
2403 if node.master_candidate:
2404 mc_now += 1
2405 mc_should = min(mc_max, self._ConfigData().cluster.candidate_pool_size)
2406 return (mc_now, mc_should, mc_max)
2407
2408 @ConfigSync(shared=1)
2409 def GetMasterCandidateStats(self, exceptions=None):
2410 """Get the number of current and maximum possible candidates.
2411
2412 This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
2413
2414 @type exceptions: list
2415 @param exceptions: if passed, list of nodes that should be ignored
2416 @rtype: tuple
2417 @return: tuple of (current, max)
2418
2419 """
2420 return self._UnlockedGetMasterCandidateStats(exceptions)
2421
2422 @ConfigSync()
2423 def MaintainCandidatePool(self, exception_node_uuids):
2424 """Try to grow the candidate pool to the desired size.
2425
2426 @type exception_node_uuids: list
2427 @param exception_node_uuids: if passed, list of nodes that should be ignored
2428 @rtype: list
2429 @return: list with the adjusted nodes (L{objects.Node} instances)
2430
2431 """
2432 mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(
2433 exception_node_uuids)
2434 mod_list = []
2435 if mc_now < mc_max:
2436 node_list = self._ConfigData().nodes.keys()
2437 random.shuffle(node_list)
2438 for uuid in node_list:
2439 if mc_now >= mc_max:
2440 break
2441 node = self._ConfigData().nodes[uuid]
2442 if (node.master_candidate or node.offline or node.drained or
2443 node.uuid in exception_node_uuids or not node.master_capable):
2444 continue
2445 mod_list.append(node)
2446 node.master_candidate = True
2447 node.serial_no += 1
2448 mc_now += 1
2449 if mc_now != mc_max:
2450 # this should not happen
2451 logging.warning("Warning: MaintainCandidatePool didn't manage to"
2452 " fill the candidate pool (%d/%d)", mc_now, mc_max)
2453 if mod_list:
2454 self._ConfigData().cluster.serial_no += 1
2455
2456 return mod_list
2457
2458 def _UnlockedAddNodeToGroup(self, node_uuid, nodegroup_uuid):
2459 """Add a given node to the specified group.
2460
2461 """
2462 if nodegroup_uuid not in self._ConfigData().nodegroups:
2463 # This can happen if a node group gets deleted between its lookup and
2464 # when we're adding the first node to it, since we don't keep a lock in
2465 # the meantime. It's ok though, as we'll fail cleanly if the node group
2466 # is not found anymore.
2467 raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
2468 if node_uuid not in self._ConfigData().nodegroups[nodegroup_uuid].members:
2469 self._ConfigData().nodegroups[nodegroup_uuid].members.append(node_uuid)
2470
2471 def _UnlockedRemoveNodeFromGroup(self, node):
2472 """Remove a given node from its group.
2473
2474 """
2475 nodegroup = node.group
2476 if nodegroup not in self._ConfigData().nodegroups:
2477 logging.warning("Warning: node '%s' has unknown node group '%s'"
2478 " (while being removed from it)", node.uuid, nodegroup)
2479 nodegroup_obj = self._ConfigData().nodegroups[nodegroup]
2480 if node.uuid not in nodegroup_obj.members:
2481 logging.warning("Warning: node '%s' not a member of its node group '%s'"
2482 " (while being removed from it)", node.uuid, nodegroup)
2483 else:
2484 nodegroup_obj.members.remove(node.uuid)
2485
2486 @ConfigSync()
2487 def AssignGroupNodes(self, mods):
2488 """Changes the group of a number of nodes.
2489
2490 @type mods: list of tuples; (node name, new group UUID)
2491 @param mods: Node membership modifications
2492
2493 """
2494 groups = self._ConfigData().nodegroups
2495 nodes = self._ConfigData().nodes
2496
2497 resmod = []
2498
2499 # Try to resolve UUIDs first
2500 for (node_uuid, new_group_uuid) in mods:
2501 try:
2502 node = nodes[node_uuid]
2503 except KeyError:
2504 raise errors.ConfigurationError("Unable to find node '%s'" % node_uuid)
2505
2506 if node.group == new_group_uuid:
2507 # Node is being assigned to its current group
2508 logging.debug("Node '%s' was assigned to its current group (%s)",
2509 node_uuid, node.group)
2510 continue
2511
2512 # Try to find current group of node
2513 try:
2514 old_group = groups[node.group]
2515 except KeyError:
2516 raise errors.ConfigurationError("Unable to find old group '%s'" %
2517 node.group)
2518
2519 # Try to find new group for node
2520 try:
2521 new_group = groups[new_group_uuid]
2522 except KeyError:
2523 raise errors.ConfigurationError("Unable to find new group '%s'" %
2524 new_group_uuid)
2525
2526 assert node.uuid in old_group.members, \
2527 ("Inconsistent configuration: node '%s' not listed in members for its"
2528 " old group '%s'" % (node.uuid, old_group.uuid))
2529 assert node.uuid not in new_group.members, \
2530 ("Inconsistent configuration: node '%s' already listed in members for"
2531 " its new group '%s'" % (node.uuid, new_group.uuid))
2532
2533 resmod.append((node, old_group, new_group))
2534
2535 # Apply changes
2536 for (node, old_group, new_group) in resmod:
2537 assert node.uuid != new_group.uuid and old_group.uuid != new_group.uuid, \
2538 "Assigning to current group is not possible"
2539
2540 node.group = new_group.uuid
2541
2542 # Update members of involved groups
2543 if node.uuid in old_group.members:
2544 old_group.members.remove(node.uuid)
2545 if node.uuid not in new_group.members:
2546 new_group.members.append(node.uuid)
2547
2548 # Update timestamps and serials (only once per node/group object)
2549 now = time.time()
2550 for obj in frozenset(itertools.chain(*resmod)):
2551 obj.serial_no += 1
2552 obj.mtime = now
2553
2554 # Force ssconf update
2555 self._ConfigData().cluster.serial_no += 1
2556
2557 def _BumpSerialNo(self):
2558 """Bump up the serial number of the config.
2559
2560 """
2561 self._ConfigData().serial_no += 1
2562 self._ConfigData().mtime = time.time()
2563
2564 def _AllUUIDObjects(self):
2565 """Returns all objects with uuid attributes.
2566
2567 """
2568 return (self._ConfigData().instances.values() +
2569 self._ConfigData().nodes.values() +
2570 self._ConfigData().nodegroups.values() +
2571 self._ConfigData().networks.values() +
2572 self._ConfigData().disks.values() +
2573 self._AllNICs() +
2574 [self._ConfigData().cluster])
2575
2576 def GetConfigManager(self, shared=False, forcelock=False):
2577 """Returns a ConfigManager, which is suitable to perform a synchronized
2578 block of configuration operations.
2579
2580 WARNING: This blocks all other configuration operations, so anything that
2581 runs inside the block should be very fast, preferably not using any IO.
2582 """
2583
2584 return ConfigManager(self, shared=shared, forcelock=forcelock)
2585
2586 def _AddLockCount(self, count):
2587 self._lock_count += count
2588 return self._lock_count
2589
2590 def _LockCount(self):
2591 return self._lock_count
2592
2593 def _OpenConfig(self, shared, force=False):
2594 """Read the config data from WConfd or disk.
2595
2596 """
2597 if self._AddLockCount(1) > 1:
2598 if self._lock_current_shared and not shared:
2599 self._AddLockCount(-1)
2600 raise errors.ConfigurationError("Can't request an exclusive"
2601 " configuration lock while holding"
2602 " shared")
2603 elif not force or self._lock_forced or not shared or self._offline:
2604 return # we already have the lock, do nothing
2605 else:
2606 self._lock_current_shared = shared
2607 if force:
2608 self._lock_forced = True
2609 # Read the configuration data. If offline, read the file directly.
2610 # If online, call WConfd.
2611 if self._offline:
2612 try:
2613 raw_data = utils.ReadFile(self._cfg_file)
2614 data_dict = serializer.Load(raw_data)
2615 # Make sure the configuration has the right version
2616 ValidateConfig(data_dict)
2617 data = objects.ConfigData.FromDict(data_dict)
2618 except errors.ConfigVersionMismatch:
2619 raise
2620 except Exception, err:
2621 raise errors.ConfigurationError(err)
2622
2623 self._cfg_id = utils.GetFileID(path=self._cfg_file)
2624
2625 if (not hasattr(data, "cluster") or
2626 not hasattr(data.cluster, "rsahostkeypub")):
2627 raise errors.ConfigurationError("Incomplete configuration"
2628 " (missing cluster.rsahostkeypub)")
2629
2630 if not data.cluster.master_node in data.nodes:
2631 msg = ("The configuration denotes node %s as master, but does not"
2632 " contain information about this node" %
2633 data.cluster.master_node)
2634 raise errors.ConfigurationError(msg)
2635
2636 master_info = data.nodes[data.cluster.master_node]
2637 if master_info.name != self._my_hostname and not self._accept_foreign:
2638 msg = ("The configuration denotes node %s as master, while my"
2639 " hostname is %s; opening a foreign configuration is only"
2640 " possible in accept_foreign mode" %
2641 (master_info.name, self._my_hostname))
2642 raise errors.ConfigurationError(msg)
2643
2644 self._SetConfigData(data)
2645
2646 # Upgrade configuration if needed
2647 self._UpgradeConfig(saveafter=True)
2648 else:
2649 if shared and not force:
2650 if self._config_data is None:
2651 logging.debug("Requesting config, as I have no up-to-date copy")
2652 dict_data = self._wconfd.ReadConfig()
2653 logging.debug("Configuration received")
2654 else:
2655 dict_data = None
2656 else:
2657 # poll until we acquire the lock
2658 while True:
2659 logging.debug("Receiving config from WConfd.LockConfig [shared=%s]",
2660 bool(shared))
2661 dict_data = \
2662 self._wconfd.LockConfig(self._GetWConfdContext(), bool(shared))
2663 if dict_data is not None:
2664 logging.debug("Received config from WConfd.LockConfig")
2665 break
2666 time.sleep(random.random())
2667
2668 try:
2669 if dict_data is not None:
2670 self._SetConfigData(objects.ConfigData.FromDict(dict_data))
2671 self._UpgradeConfig()
2672 except Exception, err:
2673 raise errors.ConfigurationError(err)
2674
2675 def _CloseConfig(self, save):
2676 """Release resources relating the config data.
2677
2678 """
2679 if self._AddLockCount(-1) > 0:
2680 return # we still have the lock, do nothing
2681 if save:
2682 try:
2683 logging.debug("Writing configuration and unlocking it")
2684 self._WriteConfig(releaselock=True)
2685 logging.debug("Configuration write, unlock finished")
2686 except Exception, err:
2687 logging.critical("Can't write the configuration: %s", str(err))
2688 raise
2689 elif not self._offline and \
2690 not (self._lock_current_shared and not self._lock_forced):
2691 logging.debug("Unlocking configuration without writing")
2692 self._wconfd.UnlockConfig(self._GetWConfdContext())
2693 self._lock_forced = False
2694
2695 # TODO: To WConfd
2696 def _UpgradeConfig(self, saveafter=False):
2697 """Run any upgrade steps.
2698
2699 This method performs both in-object upgrades and also update some data
2700 elements that need uniqueness across the whole configuration or interact
2701 with other objects.
2702
2703 @warning: if 'saveafter' is 'True', this function will call
2704 L{_WriteConfig()} so it needs to be called only from a
2705 "safe" place.
2706
2707 """
2708 # Keep a copy of the persistent part of _config_data to check for changes
2709 # Serialization doesn't guarantee order in dictionaries
2710 if saveafter:
2711 oldconf = copy.deepcopy(self._ConfigData().ToDict())
2712 else:
2713 oldconf = None
2714
2715 # In-object upgrades
2716 self._ConfigData().UpgradeConfig()
2717
2718 for item in self._AllUUIDObjects():
2719 if item.uuid is None:
2720 item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
2721 if not self._ConfigData().nodegroups:
2722 default_nodegroup_name = constants.INITIAL_NODE_GROUP_NAME
2723 default_nodegroup = objects.NodeGroup(name=default_nodegroup_name,
2724 members=[])
2725 self._UnlockedAddNodeGroup(default_nodegroup, _UPGRADE_CONFIG_JID, True)
2726 for node in self._ConfigData().nodes.values():
2727 if not node.group:
2728 node.group = self._UnlockedLookupNodeGroup(None)
2729 # This is technically *not* an upgrade, but needs to be done both when
2730 # nodegroups are being added, and upon normally loading the config,
2731 # because the members list of a node group is discarded upon
2732 # serializing/deserializing the object.
2733 self._UnlockedAddNodeToGroup(node.uuid, node.group)
2734
2735 if saveafter:
2736 modified = (oldconf != self._ConfigData().ToDict())
2737 else:
2738 modified = True # can't prove it didn't change, but doesn't matter
2739 if modified and saveafter:
2740 self._WriteConfig()
2741 self._UnlockedDropECReservations(_UPGRADE_CONFIG_JID)
2742 else:
2743 if self._offline:
2744 self._UnlockedVerifyConfigAndLog()
2745
2746 def _WriteConfig(self, destination=None, releaselock=False):
2747 """Write the configuration data to persistent storage.
2748
2749 """
2750 if destination is None:
2751 destination = self._cfg_file
2752
2753 # Save the configuration data. If offline, write the file directly.
2754 # If online, call WConfd.
2755 if self._offline:
2756 self._BumpSerialNo()
2757 txt = serializer.DumpJson(
2758 self._ConfigData().ToDict(_with_private=True),
2759 private_encoder=serializer.EncodeWithPrivateFields
2760 )
2761
2762 getents = self._getents()
2763 try:
2764 fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
2765 close=False, gid=getents.confd_gid, mode=0640)
2766 except errors.LockError:
2767 raise errors.ConfigurationError("The configuration file has been"
2768 " modified since the last write, cannot"
2769 " update")
2770 try:
2771 self._cfg_id = utils.GetFileID(fd=fd)
2772 finally:
2773 os.close(fd)
2774 else:
2775 try:
2776 if releaselock:
2777 res = self._wconfd.WriteConfigAndUnlock(self._GetWConfdContext(),
2778 self._ConfigData().ToDict())
2779 if not res:
2780 logging.warning("WriteConfigAndUnlock indicates we already have"
2781 " released the lock; assuming this was just a retry"
2782 " and the initial call succeeded")
2783 else:
2784 self._wconfd.WriteConfig(self._GetWConfdContext(),
2785 self._ConfigData().ToDict())
2786 except errors.LockError:
2787 raise errors.ConfigurationError("The configuration file has been"
2788 " modified since the last write, cannot"
2789 " update")
2790
2791 self.write_count += 1
2792
2793 def _GetAllHvparamsStrings(self, hypervisors):
2794 """Get the hvparams of all given hypervisors from the config.
2795
2796 @type hypervisors: list of string
2797 @param hypervisors: list of hypervisor names
2798 @rtype: dict of strings
2799 @returns: dictionary mapping the hypervisor name to a string representation
2800 of the hypervisor's hvparams
2801
2802 """
2803 hvparams = {}
2804 for hv in hypervisors:
2805 hvparams[hv] = self._UnlockedGetHvparamsString(hv)
2806 return hvparams
2807
2808 @staticmethod
2809 def _ExtendByAllHvparamsStrings(ssconf_values, all_hvparams):
2810 """Extends the ssconf_values dictionary by hvparams.
2811
2812 @type ssconf_values: dict of strings
2813 @param ssconf_values: dictionary mapping ssconf_keys to strings
2814 representing the content of ssconf files
2815 @type all_hvparams: dict of strings
2816 @param all_hvparams: dictionary mapping hypervisor names to a string
2817 representation of their hvparams
2818 @rtype: same as ssconf_values
2819 @returns: the ssconf_values dictionary extended by hvparams
2820
2821 """
2822 for hv in all_hvparams:
2823 ssconf_key = constants.SS_HVPARAMS_PREF + hv
2824 ssconf_values[ssconf_key] = all_hvparams[hv]
2825 return ssconf_values
2826
2827 def _UnlockedGetSshPortMap(self, node_infos):
2828 node_ports = dict([(node.name,
2829 self._UnlockedGetNdParams(node).get(
2830 constants.ND_SSH_PORT))
2831 for node in node_infos])
2832 return node_ports
2833
2834 def _UnlockedGetSsconfValues(self):
2835 """Return the values needed by ssconf.
2836
2837 @rtype: dict
2838 @return: a dictionary with keys the ssconf names and values their
2839 associated value
2840
2841 """
2842 fn = "\n".join
2843 instance_names = utils.NiceSort(
2844 [inst.name for inst in
2845 self._UnlockedGetAllInstancesInfo().values()])
2846 node_infos = self._UnlockedGetAllNodesInfo().values()
2847 node_names = [node.name for node in node_infos]
2848 node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
2849 for ninfo in node_infos]
2850 node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
2851 for ninfo in node_infos]
2852 node_vm_capable = ["%s=%s" % (ninfo.name, str(ninfo.vm_capable))
2853 for ninfo in node_infos]
2854
2855 instance_data = fn(instance_names)
2856 off_data = fn(node.name for node in node_infos if node.offline)
2857 on_data = fn(node.name for node in node_infos if not node.offline)
2858 mc_data = fn(node.name for node in node_infos if node.master_candidate)
2859 mc_ips_data = fn(node.primary_ip for node in node_infos
2860 if node.master_candidate)
2861 node_data = fn(node_names)
2862 node_pri_ips_data = fn(node_pri_ips)
2863 node_snd_ips_data = fn(node_snd_ips)
2864 node_vm_capable_data = fn(node_vm_capable)
2865
2866 cluster = self._ConfigData().cluster
2867 cluster_tags = fn(cluster.GetTags())
2868
2869 master_candidates_certs = fn("%s=%s" % (mc_uuid, mc_cert)
2870 for mc_uuid, mc_cert
2871 in cluster.candidate_certs.items())
2872
2873 hypervisor_list = fn(cluster.enabled_hypervisors)
2874 all_hvparams = self._GetAllHvparamsStrings(constants.HYPER_TYPES)
2875
2876 uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
2877
2878 nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
2879 self._ConfigData().nodegroups.values()]
2880 nodegroups_data = fn(utils.NiceSort(nodegroups))
2881 networks = ["%s %s" % (net.uuid, net.name) for net in
2882 self._ConfigData().networks.values()]
2883 networks_data = fn(utils.NiceSort(networks))
2884
2885 ssh_ports = fn("%s=%s" % (node_name, port)
2886 for node_name, port
2887 in self._UnlockedGetSshPortMap(node_infos).items())
2888
2889 ssconf_values = {
2890 constants.SS_CLUSTER_NAME: cluster.cluster_name,
2891 constants.SS_CLUSTER_TAGS: cluster_tags,
2892 constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
2893 constants.SS_SHARED_FILE_STORAGE_DIR: cluster.shared_file_storage_dir,
2894 constants.SS_GLUSTER_STORAGE_DIR: cluster.gluster_storage_dir,
2895 constants.SS_MASTER_CANDIDATES: mc_data,
2896 constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
2897 constants.SS_MASTER_CANDIDATES_CERTS: master_candidates_certs,
2898 constants.SS_MASTER_IP: cluster.master_ip,
2899 constants.SS_MASTER_NETDEV: cluster.master_netdev,
2900 constants.SS_MASTER_NETMASK: str(cluster.master_netmask),
2901 constants.SS_MASTER_NODE: self._UnlockedGetNodeName(cluster.master_node),
2902 constants.SS_NODE_LIST: node_data,
2903 constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
2904 constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
2905 constants.SS_NODE_VM_CAPABLE: node_vm_capable_data,
2906 constants.SS_OFFLINE_NODES: off_data,
2907 constants.SS_ONLINE_NODES: on_data,
2908 constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
2909 constants.SS_INSTANCE_LIST: instance_data,
2910 constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
2911 constants.SS_HYPERVISOR_LIST: hypervisor_list,
2912 constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
2913 constants.SS_UID_POOL: uid_pool,
2914 constants.SS_NODEGROUPS: nodegroups_data,
2915 constants.SS_NETWORKS: networks_data,
2916 constants.SS_ENABLED_USER_SHUTDOWN: str(cluster.enabled_user_shutdown),
2917 constants.SS_SSH_PORTS: ssh_ports,
2918 }
2919 ssconf_values = self._ExtendByAllHvparamsStrings(ssconf_values,
2920 all_hvparams)
2921 bad_values = [(k, v) for k, v in ssconf_values.items()
2922 if not isinstance(v, (str, basestring))]
2923 if bad_values:
2924 err = utils.CommaJoin("%s=%s" % (k, v) for k, v in bad_values)
2925 raise errors.ConfigurationError("Some ssconf key(s) have non-string"
2926 " values: %s" % err)
2927 return ssconf_values
2928
2929 @ConfigSync(shared=1)
2930 def GetSsconfValues(self):
2931 """Wrapper using lock around _UnlockedGetSsconf().
2932
2933 """
2934 return self._UnlockedGetSsconfValues()
2935
2936 @ConfigSync(shared=1)
2937 def GetVGName(self):
2938 """Return the volume group name.
2939
2940 """
2941 return self._ConfigData().cluster.volume_group_name
2942
2943 @ConfigSync()
2944 def SetVGName(self, vg_name):
2945 """Set the volume group name.
2946
2947 """
2948 self._ConfigData().cluster.volume_group_name = vg_name
2949 self._ConfigData().cluster.serial_no += 1
2950
2951 @ConfigSync(shared=1)
2952 def GetDRBDHelper(self):
2953 """Return DRBD usermode helper.
2954
2955 """
2956 return self._ConfigData().cluster.drbd_usermode_helper
2957
2958 @ConfigSync()
2959 def SetDRBDHelper(self, drbd_helper):
2960 """Set DRBD usermode helper.
2961
2962 """
2963 self._ConfigData().cluster.drbd_usermode_helper = drbd_helper
2964 self._ConfigData().cluster.serial_no += 1
2965
2966 @ConfigSync(shared=1)
2967 def GetMACPrefix(self):
2968 """Return the mac prefix.
2969
2970 """
2971 return self._ConfigData().cluster.mac_prefix
2972
2973 @ConfigSync(shared=1)
2974 def GetClusterInfo(self):
2975 """Returns information about the cluster
2976
2977 @rtype: L{objects.Cluster}
2978 @return: the cluster object
2979
2980 """
2981 return self._ConfigData().cluster
2982
2983 @ConfigSync(shared=1)
2984 def DisksOfType(self, dev_type):
2985 """Check if in there is at disk of the given type in the configuration.
2986
2987 """
2988 return self._ConfigData().DisksOfType(dev_type)
2989
2990 @ConfigSync(shared=1)
2991 def GetDetachedConfig(self):
2992 """Returns a detached version of a ConfigManager, which represents
2993 a read-only snapshot of the configuration at this particular time.
2994
2995 """
2996 return DetachedConfig(self._ConfigData())
2997
2998 def Update(self, target, feedback_fn, ec_id=None):
2999 """Notify function to be called after updates.
3000
3001 This function must be called when an object (as returned by
3002 GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
3003 caller wants the modifications saved to the backing store. Note
3004 that all modified objects will be saved, but the target argument
3005 is the one the caller wants to ensure that it's saved.
3006
3007 @param target: an instance of either L{objects.Cluster},
3008 L{objects.Node} or L{objects.Instance} which is existing in
3009 the cluster
3010 @param feedback_fn: Callable feedback function
3011
3012 """
3013
3014 update_function = None
3015 if isinstance(target, objects.Cluster):
3016 if self._offline:
3017 self.UpdateOfflineCluster(target, feedback_fn)
3018 return
3019 else:
3020 update_function = self._wconfd.UpdateCluster
3021 elif isinstance(target, objects.Node):
3022 update_function = self._wconfd.UpdateNode
3023 elif isinstance(target, objects.Instance):
3024 update_function = self._wconfd.UpdateInstance
3025 elif isinstance(target, objects.NodeGroup):
3026 update_function = self._wconfd.UpdateNodeGroup
3027 elif isinstance(target, objects.Network):
3028 update_function = self._wconfd.UpdateNetwork
3029 elif isinstance(target, objects.Disk):
3030 update_function = self._wconfd.UpdateDisk
3031 else:
3032 raise errors.ProgrammerError("Invalid object type (%s) passed to"
3033 " ConfigWriter.Update" % type(target))
3034
3035 def WithRetry():
3036 result = update_function(target.ToDict())
3037 self.OutDate()
3038
3039 if result is None:
3040 raise utils.RetryAgain()
3041 else:
3042 return result
3043 vals = utils.Retry(WithRetry, 0.1, 30)
3044 self.OutDate()
3045 target.serial_no = vals[0]
3046 target.mtime = float(vals[1])
3047
3048 if ec_id is not None:
3049 # Commit all ips reserved by OpInstanceSetParams and OpGroupSetParams
3050 # FIXME: After RemoveInstance is moved to WConfd, use its internal
3051 # functions from TempRes module.
3052 self.CommitTemporaryIps(ec_id)
3053
3054 # Just verify the configuration with our feedback function.
3055 # It will get written automatically by the decorator.
3056 self.VerifyConfigAndLog(feedback_fn=feedback_fn)
3057
3058 @ConfigSync()
3059 def UpdateOfflineCluster(self, target, feedback_fn):
3060 self._ConfigData().cluster = target
3061 target.serial_no += 1
3062 target.mtime = time.time()
3063 self.VerifyConfigAndLog(feedback_fn=feedback_fn)
3064
3065 def _UnlockedDropECReservations(self, _ec_id):
3066 """Drop per-execution-context reservations
3067
3068 """
3069 # FIXME: Remove the following two lines after all reservations are moved to
3070 # wconfd.
3071 for rm in self._all_rms:
3072 rm.DropECReservations(_ec_id)
3073 if not self._offline:
3074 self._wconfd.DropAllReservations(self._GetWConfdContext())
3075
3076 def DropECReservations(self, ec_id):
3077 self._UnlockedDropECReservations(ec_id)
3078
3079 @ConfigSync(shared=1)
3080 def GetAllNetworksInfo(self):
3081 """Get configuration info of all the networks.
3082
3083 """
3084 return dict(self._ConfigData().networks)
3085
3086 def _UnlockedGetNetworkList(self):
3087 """Get the list of networks.
3088
3089 This function is for internal use, when the config lock is already held.
3090
3091 """
3092 return self._ConfigData().networks.keys()
3093
3094 @ConfigSync(shared=1)
3095 def GetNetworkList(self):
3096 """Get the list of networks.
3097
3098 @return: array of networks, ex. ["main", "vlan100", "200]
3099
3100 """
3101 return self._UnlockedGetNetworkList()
3102
3103 @ConfigSync(shared=1)
3104 def GetNetworkNames(self):
3105 """Get a list of network names
3106
3107 """
3108 names = [net.name
3109 for net in self._ConfigData().networks.values()]
3110 return names
3111
3112 def _UnlockedGetNetwork(self, uuid):
3113 """Returns information about a network.
3114
3115 This function is for internal use, when the config lock is already held.
3116
3117 """
3118 if uuid not in self._ConfigData().networks:
3119 return None
3120
3121 return self._ConfigData().networks[uuid]
3122
3123 @ConfigSync(shared=1)
3124 def GetNetwork(self, uuid):
3125 """Returns information about a network.
3126
3127 It takes the information from the configuration file.
3128
3129 @param uuid: UUID of the network
3130
3131 @rtype: L{objects.Network}
3132 @return: the network object
3133
3134 """
3135 return self._UnlockedGetNetwork(uuid)
3136
3137 @ConfigSync()
3138 def AddNetwork(self, net, ec_id, check_uuid=True):
3139 """Add a network to the configuration.
3140
3141 @type net: L{objects.Network}
3142 @param net: the Network object to add
3143 @type ec_id: string
3144 @param ec_id: unique id for the job to use when creating a missing UUID
3145
3146 """
3147 self._UnlockedAddNetwork(net, ec_id, check_uuid)
3148
3149 def _UnlockedAddNetwork(self, net, ec_id, check_uuid):
3150 """Add a network to the configuration.
3151
3152 """
3153 logging.info("Adding network %s to configuration", net.name)
3154
3155 if check_uuid:
3156 self._EnsureUUID(net, ec_id)
3157
3158 net.serial_no = 1
3159 net.ctime = net.mtime = time.time()
3160 self._ConfigData().networks[net.uuid] = net
3161 self._ConfigData().cluster.serial_no += 1
3162
3163 def _UnlockedLookupNetwork(self, target):
3164 """Lookup a network's UUID.
3165
3166 @type target: string
3167 @param target: network name or UUID
3168 @rtype: string
3169 @return: network UUID
3170 @raises errors.OpPrereqError: when the target network cannot be found
3171
3172 """
3173 if target is None:
3174 return None
3175 if target in self._ConfigData().networks:
3176 return target
3177 for net in self._ConfigData().networks.values():
3178 if net.name == target:
3179 return net.uuid
3180 raise errors.OpPrereqError("Network '%s' not found" % target,
3181 errors.ECODE_NOENT)
3182
3183 @ConfigSync(shared=1)
3184 def LookupNetwork(self, target):
3185 """Lookup a network's UUID.
3186
3187 This function is just a wrapper over L{_UnlockedLookupNetwork}.
3188
3189 @type target: string
3190 @param target: network name or UUID
3191 @rtype: string
3192 @return: network UUID
3193
3194 """
3195 return self._UnlockedLookupNetwork(target)
3196
3197 @ConfigSync()
3198 def RemoveNetwork(self, network_uuid):
3199 """Remove a network from the configuration.
3200
3201 @type network_uuid: string
3202 @param network_uuid: the UUID of the network to remove
3203
3204 """
3205 logging.info("Removing network %s from configuration", network_uuid)
3206
3207 if network_uuid not in self._ConfigData().networks:
3208 raise errors.ConfigurationError("Unknown network '%s'" % network_uuid)
3209
3210 del self._ConfigData().networks[network_uuid]
3211 self._ConfigData().cluster.serial_no += 1
3212
3213 def _UnlockedGetGroupNetParams(self, net_uuid, node_uuid):
3214 """Get the netparams (mode, link) of a network.
3215
3216 Get a network's netparams for a given node.
3217
3218 @type net_uuid: string
3219 @param net_uuid: network uuid
3220 @type node_uuid: string
3221 @param node_uuid: node UUID
3222 @rtype: dict or None
3223 @return: netparams
3224
3225 """