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