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