Package ganeti :: Module objects
[hide private]
[frames] | no frames]

Source Code for Module ganeti.objects

   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  """Transportable objects for Ganeti. 
  32   
  33  This module provides small, mostly data-only objects which are safe to 
  34  pass to and from external parties. 
  35   
  36  """ 
  37   
  38  # pylint: disable=E0203,W0201,R0902 
  39   
  40  # E0203: Access to member %r before its definition, since we use 
  41  # objects.py which doesn't explicitly initialise its members 
  42   
  43  # W0201: Attribute '%s' defined outside __init__ 
  44   
  45  # R0902: Allow instances of these objects to have more than 20 attributes 
  46   
  47  import ConfigParser 
  48  import re 
  49  import copy 
  50  import logging 
  51  import time 
  52  from cStringIO import StringIO 
  53   
  54  from ganeti import errors 
  55  from ganeti import constants 
  56  from ganeti import netutils 
  57  from ganeti import outils 
  58  from ganeti import utils 
  59  from ganeti import serializer 
  60   
  61  from socket import AF_INET 
  62   
  63   
  64  __all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance", 
  65             "OS", "Node", "NodeGroup", "Cluster", "FillDict", "Network", 
  66             "Filter", "Maintenance"] 
  67   
  68  _TIMESTAMPS = ["ctime", "mtime"] 
  69  _UUID = ["uuid"] 
70 71 72 -def FillDict(defaults_dict, custom_dict, skip_keys=None):
73 """Basic function to apply settings on top a default dict. 74 75 @type defaults_dict: dict 76 @param defaults_dict: dictionary holding the default values 77 @type custom_dict: dict 78 @param custom_dict: dictionary holding customized value 79 @type skip_keys: list 80 @param skip_keys: which keys not to fill 81 @rtype: dict 82 @return: dict with the 'full' values 83 84 """ 85 ret_dict = copy.deepcopy(defaults_dict) 86 ret_dict.update(custom_dict) 87 if skip_keys: 88 for k in skip_keys: 89 if k in ret_dict: 90 del ret_dict[k] 91 return ret_dict
92
93 94 -def FillIPolicy(default_ipolicy, custom_ipolicy):
95 """Fills an instance policy with defaults. 96 97 """ 98 assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS 99 ret_dict = copy.deepcopy(custom_ipolicy) 100 for key in default_ipolicy: 101 if key not in ret_dict: 102 ret_dict[key] = copy.deepcopy(default_ipolicy[key]) 103 elif key == constants.ISPECS_STD: 104 ret_dict[key] = FillDict(default_ipolicy[key], ret_dict[key]) 105 return ret_dict
106
107 108 -def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
109 """Fills the disk parameter defaults. 110 111 @see: L{FillDict} for parameters and return value 112 113 """ 114 return dict((dt, FillDict(default_dparams.get(dt, {}), 115 custom_dparams.get(dt, {}), 116 skip_keys=skip_keys)) 117 for dt in constants.DISK_TEMPLATES)
118
119 120 -def UpgradeGroupedParams(target, defaults):
121 """Update all groups for the target parameter. 122 123 @type target: dict of dicts 124 @param target: {group: {parameter: value}} 125 @type defaults: dict 126 @param defaults: default parameter values 127 128 """ 129 if target is None: 130 target = {constants.PP_DEFAULT: defaults} 131 else: 132 for group in target: 133 target[group] = FillDict(defaults, target[group]) 134 return target
135
136 137 -def UpgradeBeParams(target):
138 """Update the be parameters dict to the new format. 139 140 @type target: dict 141 @param target: "be" parameters dict 142 143 """ 144 if constants.BE_MEMORY in target: 145 memory = target[constants.BE_MEMORY] 146 target[constants.BE_MAXMEM] = memory 147 target[constants.BE_MINMEM] = memory 148 del target[constants.BE_MEMORY]
149
150 151 -def UpgradeDiskParams(diskparams):
152 """Upgrade the disk parameters. 153 154 @type diskparams: dict 155 @param diskparams: disk parameters to upgrade 156 @rtype: dict 157 @return: the upgraded disk parameters dict 158 159 """ 160 if not diskparams: 161 result = {} 162 else: 163 result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams) 164 165 return result
166
167 168 -def UpgradeNDParams(ndparams):
169 """Upgrade ndparams structure. 170 171 @type ndparams: dict 172 @param ndparams: disk parameters to upgrade 173 @rtype: dict 174 @return: the upgraded node parameters dict 175 176 """ 177 if ndparams is None: 178 ndparams = {} 179 180 if (constants.ND_OOB_PROGRAM in ndparams and 181 ndparams[constants.ND_OOB_PROGRAM] is None): 182 # will be reset by the line below 183 del ndparams[constants.ND_OOB_PROGRAM] 184 return FillDict(constants.NDC_DEFAULTS, ndparams)
185
186 187 -def MakeEmptyIPolicy():
188 """Create empty IPolicy dictionary. 189 190 """ 191 return {}
192
193 194 -class ConfigObject(outils.ValidatedSlots):
195 """A generic config object. 196 197 It has the following properties: 198 199 - provides somewhat safe recursive unpickling and pickling for its classes 200 - unset attributes which are defined in slots are always returned 201 as None instead of raising an error 202 203 Classes derived from this must always declare __slots__ (we use many 204 config objects and the memory reduction is useful) 205 206 """ 207 __slots__ = [] 208
209 - def __getattr__(self, name):
210 if name not in self.GetAllSlots(): 211 raise AttributeError("Invalid object attribute %s.%s" % 212 (type(self).__name__, name)) 213 return None
214
215 - def __setstate__(self, state):
216 slots = self.GetAllSlots() 217 for name in state: 218 if name in slots: 219 setattr(self, name, state[name])
220
221 - def Validate(self):
222 """Validates the slots. 223 224 This method returns L{None} if the validation succeeds, or raises 225 an exception otherwise. 226 227 This method must be implemented by the child classes. 228 229 @rtype: NoneType 230 @return: L{None}, if the validation succeeds 231 232 @raise Exception: validation fails 233 234 """
235
236 - def ToDict(self, _with_private=False):
237 """Convert to a dict holding only standard python types. 238 239 The generic routine just dumps all of this object's attributes in 240 a dict. It does not work if the class has children who are 241 ConfigObjects themselves (e.g. the nics list in an Instance), in 242 which case the object should subclass the function in order to 243 make sure all objects returned are only standard python types. 244 245 Private fields can be included or not with the _with_private switch. 246 The actual implementation of this switch is left for those subclassses 247 with private fields to implement. 248 249 @type _with_private: bool 250 @param _with_private: if True, the object will leak its private fields in 251 the dictionary representation. If False, the values 252 will be replaced with None. 253 254 """ 255 result = {} 256 for name in self.GetAllSlots(): 257 value = getattr(self, name, None) 258 if value is not None: 259 result[name] = value 260 return result
261 262 __getstate__ = ToDict 263 264 @classmethod
265 - def FromDict(cls, val):
266 """Create an object from a dictionary. 267 268 This generic routine takes a dict, instantiates a new instance of 269 the given class, and sets attributes based on the dict content. 270 271 As for `ToDict`, this does not work if the class has children 272 who are ConfigObjects themselves (e.g. the nics list in an 273 Instance), in which case the object should subclass the function 274 and alter the objects. 275 276 """ 277 if not isinstance(val, dict): 278 raise errors.ConfigurationError("Invalid object passed to FromDict:" 279 " expected dict, got %s" % type(val)) 280 val_str = dict([(str(k), v) for k, v in val.iteritems()]) 281 obj = cls(**val_str) # pylint: disable=W0142 282 return obj
283
284 - def Copy(self):
285 """Makes a deep copy of the current object and its children. 286 287 """ 288 dict_form = self.ToDict() 289 clone_obj = self.__class__.FromDict(dict_form) 290 return clone_obj
291
292 - def __repr__(self):
293 """Implement __repr__ for ConfigObjects.""" 294 return repr(self.ToDict())
295
296 - def __eq__(self, other):
297 """Implement __eq__ for ConfigObjects.""" 298 return isinstance(other, self.__class__) and self.ToDict() == other.ToDict()
299
300 - def UpgradeConfig(self):
301 """Fill defaults for missing configuration values. 302 303 This method will be called at configuration load time, and its 304 implementation will be object dependent. 305 306 """ 307 pass
308
309 310 -class TaggableObject(ConfigObject):
311 """An generic class supporting tags. 312 313 """ 314 __slots__ = ["tags"] 315 VALID_TAG_RE = re.compile(r"^[\w.+*/:@-]+$") 316 317 @classmethod
318 - def ValidateTag(cls, tag):
319 """Check if a tag is valid. 320 321 If the tag is invalid, an errors.TagError will be raised. The 322 function has no return value. 323 324 """ 325 if not isinstance(tag, basestring): 326 raise errors.TagError("Invalid tag type (not a string)") 327 if len(tag) > constants.MAX_TAG_LEN: 328 raise errors.TagError("Tag too long (>%d characters)" % 329 constants.MAX_TAG_LEN) 330 if not tag: 331 raise errors.TagError("Tags cannot be empty") 332 if not cls.VALID_TAG_RE.match(tag): 333 raise errors.TagError("Tag contains invalid characters")
334
335 - def GetTags(self):
336 """Return the tags list. 337 338 """ 339 tags = getattr(self, "tags", None) 340 if tags is None: 341 tags = self.tags = set() 342 return tags
343
344 - def AddTag(self, tag):
345 """Add a new tag. 346 347 """ 348 self.ValidateTag(tag) 349 tags = self.GetTags() 350 if len(tags) >= constants.MAX_TAGS_PER_OBJ: 351 raise errors.TagError("Too many tags") 352 self.GetTags().add(tag)
353
354 - def RemoveTag(self, tag):
355 """Remove a tag. 356 357 """ 358 self.ValidateTag(tag) 359 tags = self.GetTags() 360 try: 361 tags.remove(tag) 362 except KeyError: 363 raise errors.TagError("Tag not found")
364
365 - def ToDict(self, _with_private=False):
366 """Taggable-object-specific conversion to standard python types. 367 368 This replaces the tags set with a list. 369 370 """ 371 bo = super(TaggableObject, self).ToDict(_with_private=_with_private) 372 373 tags = bo.get("tags", None) 374 if isinstance(tags, set): 375 bo["tags"] = list(tags) 376 return bo
377 378 @classmethod
379 - def FromDict(cls, val):
380 """Custom function for instances. 381 382 """ 383 obj = super(TaggableObject, cls).FromDict(val) 384 if hasattr(obj, "tags") and isinstance(obj.tags, list): 385 obj.tags = set(obj.tags) 386 return obj
387
388 389 -class MasterNetworkParameters(ConfigObject):
390 """Network configuration parameters for the master 391 392 @ivar uuid: master nodes UUID 393 @ivar ip: master IP 394 @ivar netmask: master netmask 395 @ivar netdev: master network device 396 @ivar ip_family: master IP family 397 398 """ 399 __slots__ = [ 400 "uuid", 401 "ip", 402 "netmask", 403 "netdev", 404 "ip_family", 405 ]
406
407 408 -class ConfigData(ConfigObject):
409 """Top-level config object.""" 410 __slots__ = [ 411 "version", 412 "cluster", 413 "nodes", 414 "nodegroups", 415 "instances", 416 "networks", 417 "disks", 418 "filters", 419 "maintenance", 420 "serial_no", 421 ] + _TIMESTAMPS 422
423 - def ToDict(self, _with_private=False):
424 """Custom function for top-level config data. 425 426 This just replaces the list of nodes, instances, nodegroups, 427 networks, disks and the cluster with standard python types. 428 429 """ 430 mydict = super(ConfigData, self).ToDict(_with_private=_with_private) 431 mydict["cluster"] = mydict["cluster"].ToDict() 432 mydict["maintenance"] = mydict["maintenance"].ToDict() 433 for key in ("nodes", "instances", "nodegroups", "networks", "disks", 434 "filters"): 435 mydict[key] = outils.ContainerToDicts(mydict[key]) 436 437 return mydict
438 439 @classmethod
440 - def FromDict(cls, val):
441 """Custom function for top-level config data 442 443 """ 444 obj = super(ConfigData, cls).FromDict(val) 445 obj.cluster = Cluster.FromDict(obj.cluster) 446 obj.nodes = outils.ContainerFromDicts(obj.nodes, dict, Node) 447 obj.instances = \ 448 outils.ContainerFromDicts(obj.instances, dict, Instance) 449 obj.nodegroups = \ 450 outils.ContainerFromDicts(obj.nodegroups, dict, NodeGroup) 451 obj.networks = outils.ContainerFromDicts(obj.networks, dict, Network) 452 obj.disks = outils.ContainerFromDicts(obj.disks, dict, Disk) 453 obj.filters = outils.ContainerFromDicts(obj.filters, dict, Filter) 454 obj.maintenance = Maintenance.FromDict(obj.maintenance) 455 return obj
456
457 - def DisksOfType(self, dev_type):
458 """Check if in there is at disk of the given type in the configuration. 459 460 @type dev_type: L{constants.DTS_BLOCK} 461 @param dev_type: the type to look for 462 @rtype: list of disks 463 @return: all disks of the dev_type 464 465 """ 466 467 return [disk for disk in self.disks.values() 468 if disk.IsBasedOnDiskType(dev_type)]
469
470 - def UpgradeConfig(self):
471 """Fill defaults for missing configuration values. 472 473 """ 474 self.cluster.UpgradeConfig() 475 for node in self.nodes.values(): 476 node.UpgradeConfig() 477 for instance in self.instances.values(): 478 instance.UpgradeConfig() 479 self._UpgradeEnabledDiskTemplates() 480 if self.nodegroups is None: 481 self.nodegroups = {} 482 for nodegroup in self.nodegroups.values(): 483 nodegroup.UpgradeConfig() 484 InstancePolicy.UpgradeDiskTemplates( 485 nodegroup.ipolicy, self.cluster.enabled_disk_templates) 486 if self.cluster.drbd_usermode_helper is None: 487 if self.cluster.IsDiskTemplateEnabled(constants.DT_DRBD8): 488 self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER 489 if self.networks is None: 490 self.networks = {} 491 for network in self.networks.values(): 492 network.UpgradeConfig() 493 for disk in self.disks.values(): 494 disk.UpgradeConfig() 495 if self.filters is None: 496 self.filters = {} 497 if self.maintenance is None: 498 self.maintenance = Maintenance.FromDict({}) 499 self.maintenance.UpgradeConfig()
500
502 """Upgrade the cluster's enabled disk templates by inspecting the currently 503 enabled and/or used disk templates. 504 505 """ 506 if not self.cluster.enabled_disk_templates: 507 template_set = \ 508 set([d.dev_type for d in self.disks.values()]) 509 if any(not inst.disks for inst in self.instances.values()): 510 template_set.add(constants.DT_DISKLESS) 511 # Add drbd and plain, if lvm is enabled (by specifying a volume group) 512 if self.cluster.volume_group_name: 513 template_set.add(constants.DT_DRBD8) 514 template_set.add(constants.DT_PLAIN) 515 # Set enabled_disk_templates to the inferred disk templates. Order them 516 # according to a preference list that is based on Ganeti's history of 517 # supported disk templates. 518 self.cluster.enabled_disk_templates = [] 519 for preferred_template in constants.DISK_TEMPLATE_PREFERENCE: 520 if preferred_template in template_set: 521 self.cluster.enabled_disk_templates.append(preferred_template) 522 template_set.remove(preferred_template) 523 self.cluster.enabled_disk_templates.extend(list(template_set)) 524 InstancePolicy.UpgradeDiskTemplates( 525 self.cluster.ipolicy, self.cluster.enabled_disk_templates)
526
527 528 -class NIC(ConfigObject):
529 """Config object representing a network card.""" 530 __slots__ = ["name", "mac", "ip", "network", 531 "nicparams", "netinfo", "pci", "hvinfo"] + _UUID 532 533 @classmethod
534 - def CheckParameterSyntax(cls, nicparams):
535 """Check the given parameters for validity. 536 537 @type nicparams: dict 538 @param nicparams: dictionary with parameter names/value 539 @raise errors.ConfigurationError: when a parameter is not valid 540 541 """ 542 mode = nicparams[constants.NIC_MODE] 543 if (mode not in constants.NIC_VALID_MODES and 544 mode != constants.VALUE_AUTO): 545 raise errors.ConfigurationError("Invalid NIC mode '%s'" % mode) 546 547 if (mode == constants.NIC_MODE_BRIDGED and 548 not nicparams[constants.NIC_LINK]): 549 raise errors.ConfigurationError("Missing bridged NIC link")
550
551 552 -class Filter(ConfigObject):
553 """Config object representing a filter rule.""" 554 __slots__ = ["watermark", "priority", 555 "predicates", "action", "reason_trail"] + _UUID
556
557 558 -class Maintenance(ConfigObject):
559 """Config object representing the state of the maintenance daemon""" 560 __slots__ = ["roundDelay", "jobs", "evacuated", "balance", "balanceThreshold", 561 "incidents", "serial_no"] + _TIMESTAMPS 562
563 - def UpgradeConfig(self):
564 if self.serial_no is None: 565 self.serial_no = 1 566 if self.mtime is None: 567 self.mtime = time.time() 568 if self.ctime is None: 569 self.ctime = time.time()
570
571 572 -class Disk(ConfigObject):
573 """Config object representing a block device.""" 574 __slots__ = [ 575 "forthcoming", 576 "name", 577 "dev_type", 578 "logical_id", 579 "children", 580 "nodes", 581 "iv_name", 582 "size", 583 "mode", 584 "params", 585 "spindles", 586 "pci", 587 "hvinfo", 588 "serial_no", 589 # dynamic_params is special. It depends on the node this instance 590 # is sent to, and should not be persisted. 591 "dynamic_params" 592 ] + _UUID + _TIMESTAMPS 593
594 - def _ComputeAllNodes(self):
595 """Compute the list of all nodes covered by a device and its children.""" 596 def _Helper(nodes, device): 597 """Recursively compute nodes given a top device.""" 598 if device.dev_type in constants.DTS_DRBD: 599 nodes.extend(device.logical_id[:2]) 600 if device.children: 601 for child in device.children: 602 _Helper(nodes, child)
603 604 all_nodes = list() 605 _Helper(all_nodes, self) 606 return tuple(set(all_nodes))
607 608 all_nodes = property(_ComputeAllNodes, None, None, 609 "List of names of all the nodes of a disk") 610
611 - def CreateOnSecondary(self):
612 """Test if this device needs to be created on a secondary node.""" 613 return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
614
615 - def AssembleOnSecondary(self):
616 """Test if this device needs to be assembled on a secondary node.""" 617 return self.dev_type in (constants.DT_DRBD8, constants.DT_PLAIN)
618
619 - def OpenOnSecondary(self):
620 """Test if this device needs to be opened on a secondary node.""" 621 return self.dev_type in (constants.DT_PLAIN,)
622
623 - def SupportsSnapshots(self):
624 """Test if this device supports snapshots.""" 625 return self.dev_type in constants.DTS_SNAPSHOT_CAPABLE
626
627 - def StaticDevPath(self):
628 """Return the device path if this device type has a static one. 629 630 Some devices (LVM for example) live always at the same /dev/ path, 631 irrespective of their status. For such devices, we return this 632 path, for others we return None. 633 634 @warning: The path returned is not a normalized pathname; callers 635 should check that it is a valid path. 636 637 """ 638 if self.dev_type == constants.DT_PLAIN: 639 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) 640 elif self.dev_type == constants.DT_BLOCK: 641 return self.logical_id[1] 642 elif self.dev_type == constants.DT_RBD: 643 return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1]) 644 return None
645
646 - def ChildrenNeeded(self):
647 """Compute the needed number of children for activation. 648 649 This method will return either -1 (all children) or a positive 650 number denoting the minimum number of children needed for 651 activation (only mirrored devices will usually return >=0). 652 653 Currently, only DRBD8 supports diskless activation (therefore we 654 return 0), for all other we keep the previous semantics and return 655 -1. 656 657 """ 658 if self.dev_type == constants.DT_DRBD8: 659 return 0 660 return -1
661
662 - def IsBasedOnDiskType(self, dev_type):
663 """Check if the disk or its children are based on the given type. 664 665 @type dev_type: L{constants.DTS_BLOCK} 666 @param dev_type: the type to look for 667 @rtype: boolean 668 @return: boolean indicating if a device of the given type was found or not 669 670 """ 671 if self.children: 672 for child in self.children: 673 if child.IsBasedOnDiskType(dev_type): 674 return True 675 return self.dev_type == dev_type
676
677 - def GetNodes(self, node_uuid):
678 """This function returns the nodes this device lives on. 679 680 Given the node on which the parent of the device lives on (or, in 681 case of a top-level device, the primary node of the devices' 682 instance), this function will return a list of nodes on which this 683 devices needs to (or can) be assembled. 684 685 """ 686 if self.dev_type in [constants.DT_PLAIN, constants.DT_FILE, 687 constants.DT_BLOCK, constants.DT_RBD, 688 constants.DT_EXT, constants.DT_SHARED_FILE, 689 constants.DT_GLUSTER]: 690 result = [node_uuid] 691 elif self.dev_type in constants.DTS_DRBD: 692 result = [self.logical_id[0], self.logical_id[1]] 693 if node_uuid not in result: 694 raise errors.ConfigurationError("DRBD device passed unknown node") 695 else: 696 raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type) 697 return result
698
699 - def GetPrimaryNode(self, node_uuid):
700 """This function returns the primary node of the device. 701 702 If the device is not a DRBD device, we still return the node the device 703 lives on. 704 705 """ 706 if self.dev_type in constants.DTS_DRBD: 707 return self.logical_id[0] 708 return node_uuid
709
710 - def ComputeNodeTree(self, parent_node_uuid):
711 """Compute the node/disk tree for this disk and its children. 712 713 This method, given the node on which the parent disk lives, will 714 return the list of all (node UUID, disk) pairs which describe the disk 715 tree in the most compact way. For example, a drbd/lvm stack 716 will be returned as (primary_node, drbd) and (secondary_node, drbd) 717 which represents all the top-level devices on the nodes. 718 719 """ 720 my_nodes = self.GetNodes(parent_node_uuid) 721 result = [(node, self) for node in my_nodes] 722 if not self.children: 723 # leaf device 724 return result 725 for node in my_nodes: 726 for child in self.children: 727 child_result = child.ComputeNodeTree(node) 728 if len(child_result) == 1: 729 # child (and all its descendants) is simple, doesn't split 730 # over multiple hosts, so we don't need to describe it, our 731 # own entry for this node describes it completely 732 continue 733 else: 734 # check if child nodes differ from my nodes; note that 735 # subdisk can differ from the child itself, and be instead 736 # one of its descendants 737 for subnode, subdisk in child_result: 738 if subnode not in my_nodes: 739 result.append((subnode, subdisk)) 740 # otherwise child is under our own node, so we ignore this 741 # entry (but probably the other results in the list will 742 # be different) 743 return result
744
745 - def ComputeGrowth(self, amount):
746 """Compute the per-VG growth requirements. 747 748 This only works for VG-based disks. 749 750 @type amount: integer 751 @param amount: the desired increase in (user-visible) disk space 752 @rtype: dict 753 @return: a dictionary of volume-groups and the required size 754 755 """ 756 if self.dev_type == constants.DT_PLAIN: 757 return {self.logical_id[0]: amount} 758 elif self.dev_type == constants.DT_DRBD8: 759 if self.children: 760 return self.children[0].ComputeGrowth(amount) 761 else: 762 return {} 763 else: 764 # Other disk types do not require VG space 765 return {}
766
767 - def RecordGrow(self, amount):
768 """Update the size of this disk after growth. 769 770 This method recurses over the disks's children and updates their 771 size correspondigly. The method needs to be kept in sync with the 772 actual algorithms from bdev. 773 774 """ 775 if self.dev_type in (constants.DT_PLAIN, constants.DT_FILE, 776 constants.DT_RBD, constants.DT_EXT, 777 constants.DT_SHARED_FILE, constants.DT_GLUSTER): 778 self.size += amount 779 elif self.dev_type == constants.DT_DRBD8: 780 if self.children: 781 self.children[0].RecordGrow(amount) 782 self.size += amount 783 else: 784 raise errors.ProgrammerError("Disk.RecordGrow called for unsupported" 785 " disk type %s" % self.dev_type)
786
787 - def Update(self, size=None, mode=None, spindles=None):
788 """Apply changes to size, spindles and mode. 789 790 """ 791 if self.dev_type == constants.DT_DRBD8: 792 if self.children: 793 self.children[0].Update(size=size, mode=mode) 794 else: 795 assert not self.children 796 797 if size is not None: 798 self.size = size 799 if mode is not None: 800 self.mode = mode 801 if spindles is not None: 802 self.spindles = spindles
803
804 - def UnsetSize(self):
805 """Sets recursively the size to zero for the disk and its children. 806 807 """ 808 if self.children: 809 for child in self.children: 810 child.UnsetSize() 811 self.size = 0
812
813 - def UpdateDynamicDiskParams(self, target_node_uuid, nodes_ip):
814 """Updates the dynamic disk params for the given node. 815 816 This is mainly used for drbd, which needs ip/port configuration. 817 818 Arguments: 819 - target_node_uuid: the node UUID we wish to configure for 820 - nodes_ip: a mapping of node name to ip 821 822 The target_node must exist in nodes_ip, and should be one of the 823 nodes in the logical ID if this device is a DRBD device. 824 825 """ 826 if self.children: 827 for child in self.children: 828 child.UpdateDynamicDiskParams(target_node_uuid, nodes_ip) 829 830 dyn_disk_params = {} 831 if self.logical_id is not None and self.dev_type in constants.DTS_DRBD: 832 pnode_uuid, snode_uuid, _, pminor, sminor, _ = self.logical_id 833 if target_node_uuid not in (pnode_uuid, snode_uuid): 834 # disk object is being sent to neither the primary nor the secondary 835 # node. reset the dynamic parameters, the target node is not 836 # supposed to use them. 837 self.dynamic_params = dyn_disk_params 838 return 839 840 pnode_ip = nodes_ip.get(pnode_uuid, None) 841 snode_ip = nodes_ip.get(snode_uuid, None) 842 if pnode_ip is None or snode_ip is None: 843 raise errors.ConfigurationError("Can't find primary or secondary node" 844 " for %s" % str(self)) 845 if pnode_uuid == target_node_uuid: 846 dyn_disk_params[constants.DDP_LOCAL_IP] = pnode_ip 847 dyn_disk_params[constants.DDP_REMOTE_IP] = snode_ip 848 dyn_disk_params[constants.DDP_LOCAL_MINOR] = pminor 849 dyn_disk_params[constants.DDP_REMOTE_MINOR] = sminor 850 else: # it must be secondary, we tested above 851 dyn_disk_params[constants.DDP_LOCAL_IP] = snode_ip 852 dyn_disk_params[constants.DDP_REMOTE_IP] = pnode_ip 853 dyn_disk_params[constants.DDP_LOCAL_MINOR] = sminor 854 dyn_disk_params[constants.DDP_REMOTE_MINOR] = pminor 855 856 self.dynamic_params = dyn_disk_params
857 858 # pylint: disable=W0221
859 - def ToDict(self, include_dynamic_params=False, 860 _with_private=False):
861 """Disk-specific conversion to standard python types. 862 863 This replaces the children lists of objects with lists of 864 standard python types. 865 866 """ 867 bo = super(Disk, self).ToDict(_with_private=_with_private) 868 if not include_dynamic_params and "dynamic_params" in bo: 869 del bo["dynamic_params"] 870 871 if _with_private and "logical_id" in bo: 872 mutable_id = list(bo["logical_id"]) 873 mutable_id[5] = mutable_id[5].Get() 874 bo["logical_id"] = tuple(mutable_id) 875 876 for attr in ("children",): 877 alist = bo.get(attr, None) 878 if alist: 879 bo[attr] = outils.ContainerToDicts(alist) 880 return bo
881 882 @classmethod
883 - def FromDict(cls, val):
884 """Custom function for Disks 885 886 """ 887 obj = super(Disk, cls).FromDict(val) 888 if obj.children: 889 obj.children = outils.ContainerFromDicts(obj.children, list, Disk) 890 if obj.logical_id and isinstance(obj.logical_id, list): 891 obj.logical_id = tuple(obj.logical_id) 892 if obj.dev_type in constants.DTS_DRBD: 893 # we need a tuple of length six here 894 if len(obj.logical_id) < 6: 895 obj.logical_id += (None,) * (6 - len(obj.logical_id)) 896 # If we do have a tuple of length 6, make the last entry (secret key) 897 # private 898 elif (len(obj.logical_id) == 6 and 899 not isinstance(obj.logical_id[-1], serializer.Private)): 900 obj.logical_id = obj.logical_id[:-1] + \ 901 (serializer.Private(obj.logical_id[-1]),) 902 return obj
903
904 - def __str__(self):
905 """Custom str() formatter for disks. 906 907 """ 908 if self.dev_type == constants.DT_PLAIN: 909 val = "<LogicalVolume(/dev/%s/%s" % self.logical_id 910 elif self.dev_type in constants.DTS_DRBD: 911 node_a, node_b, port, minor_a, minor_b = self.logical_id[:5] 912 val = "<DRBD8(" 913 914 val += ("hosts=%s/%d-%s/%d, port=%s, " % 915 (node_a, minor_a, node_b, minor_b, port)) 916 if self.children and self.children.count(None) == 0: 917 val += "backend=%s, metadev=%s" % (self.children[0], self.children[1]) 918 else: 919 val += "no local storage" 920 else: 921 val = ("<Disk(type=%s, logical_id=%s, children=%s" % 922 (self.dev_type, self.logical_id, self.children)) 923 if self.iv_name is None: 924 val += ", not visible" 925 else: 926 val += ", visible as /dev/%s" % self.iv_name 927 if self.spindles is not None: 928 val += ", spindles=%s" % self.spindles 929 if isinstance(self.size, int): 930 val += ", size=%dm)>" % self.size 931 else: 932 val += ", size='%s')>" % (self.size,) 933 return val
934
935 - def Verify(self):
936 """Checks that this disk is correctly configured. 937 938 """ 939 all_errors = [] 940 if self.mode not in constants.DISK_ACCESS_SET: 941 all_errors.append("Disk access mode '%s' is invalid" % (self.mode, )) 942 return all_errors
943
944 - def UpgradeConfig(self):
945 """Fill defaults for missing configuration values. 946 947 """ 948 if self.children: 949 for child in self.children: 950 child.UpgradeConfig() 951 952 # FIXME: Make this configurable in Ganeti 2.7 953 # Params should be an empty dict that gets filled any time needed 954 # In case of ext template we allow arbitrary params that should not 955 # be overrided during a config reload/upgrade. 956 if not self.params or not isinstance(self.params, dict): 957 self.params = {} 958 959 # add here config upgrade for this disk 960 if self.serial_no is None: 961 self.serial_no = 1 962 if self.mtime is None: 963 self.mtime = time.time() 964 if self.ctime is None: 965 self.ctime = time.time() 966 967 # map of legacy device types (mapping differing LD constants to new 968 # DT constants) 969 LEG_DEV_TYPE_MAP = {"lvm": constants.DT_PLAIN, "drbd8": constants.DT_DRBD8} 970 if self.dev_type in LEG_DEV_TYPE_MAP: 971 self.dev_type = LEG_DEV_TYPE_MAP[self.dev_type]
972 973 @staticmethod
974 - def ComputeLDParams(disk_template, disk_params):
975 """Computes Logical Disk parameters from Disk Template parameters. 976 977 @type disk_template: string 978 @param disk_template: disk template, one of L{constants.DISK_TEMPLATES} 979 @type disk_params: dict 980 @param disk_params: disk template parameters; 981 dict(template_name -> parameters 982 @rtype: list(dict) 983 @return: a list of dicts, one for each node of the disk hierarchy. Each dict 984 contains the LD parameters of the node. The tree is flattened in-order. 985 986 """ 987 if disk_template not in constants.DISK_TEMPLATES: 988 raise errors.ProgrammerError("Unknown disk template %s" % disk_template) 989 990 assert disk_template in disk_params 991 992 result = list() 993 dt_params = disk_params[disk_template] 994 995 if disk_template == constants.DT_DRBD8: 996 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_DRBD8], { 997 constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE], 998 constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS], 999 constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS], 1000 constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG], 1001 constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM], 1002 constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM], 1003 constants.LDP_PROTOCOL: dt_params[constants.DRBD_PROTOCOL], 1004 constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC], 1005 constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD], 1006 constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET], 1007 constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET], 1008 constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE], 1009 constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE], 1010 })) 1011 1012 # data LV 1013 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], { 1014 constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES], 1015 })) 1016 1017 # metadata LV 1018 result.append(FillDict(constants.DISK_LD_DEFAULTS[constants.DT_PLAIN], { 1019 constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES], 1020 })) 1021 1022 else: 1023 defaults = constants.DISK_LD_DEFAULTS[disk_template] 1024 values = {} 1025 for field in defaults: 1026 values[field] = dt_params[field] 1027 result.append(FillDict(defaults, values)) 1028 1029 return result
1030
1031 1032 -class InstancePolicy(ConfigObject):
1033 """Config object representing instance policy limits dictionary. 1034 1035 Note that this object is not actually used in the config, it's just 1036 used as a placeholder for a few functions. 1037 1038 """ 1039 @classmethod
1040 - def UpgradeDiskTemplates(cls, ipolicy, enabled_disk_templates):
1041 """Upgrades the ipolicy configuration.""" 1042 if constants.IPOLICY_DTS in ipolicy: 1043 if not set(ipolicy[constants.IPOLICY_DTS]).issubset( 1044 set(enabled_disk_templates)): 1045 ipolicy[constants.IPOLICY_DTS] = list( 1046 set(ipolicy[constants.IPOLICY_DTS]) & set(enabled_disk_templates))
1047 1048 @classmethod
1049 - def CheckParameterSyntax(cls, ipolicy, check_std):
1050 """ Check the instance policy for validity. 1051 1052 @type ipolicy: dict 1053 @param ipolicy: dictionary with min/max/std specs and policies 1054 @type check_std: bool 1055 @param check_std: Whether to check std value or just assume compliance 1056 @raise errors.ConfigurationError: when the policy is not legal 1057 1058 """ 1059 InstancePolicy.CheckISpecSyntax(ipolicy, check_std) 1060 if constants.IPOLICY_DTS in ipolicy: 1061 InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS]) 1062 for key in constants.IPOLICY_PARAMETERS: 1063 if key in ipolicy: 1064 InstancePolicy.CheckParameter(key, ipolicy[key]) 1065 wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS 1066 if wrong_keys: 1067 raise errors.ConfigurationError("Invalid keys in ipolicy: %s" % 1068 utils.CommaJoin(wrong_keys))
1069 1070 @classmethod
1071 - def _CheckIncompleteSpec(cls, spec, keyname):
1072 missing_params = constants.ISPECS_PARAMETERS - frozenset(spec.keys()) 1073 if missing_params: 1074 msg = ("Missing instance specs parameters for %s: %s" % 1075 (keyname, utils.CommaJoin(missing_params))) 1076 raise errors.ConfigurationError(msg)
1077 1078 @classmethod
1079 - def CheckISpecSyntax(cls, ipolicy, check_std):
1080 """Check the instance policy specs for validity. 1081 1082 @type ipolicy: dict 1083 @param ipolicy: dictionary with min/max/std specs 1084 @type check_std: bool 1085 @param check_std: Whether to check std value or just assume compliance 1086 @raise errors.ConfigurationError: when specs are not valid 1087 1088 """ 1089 if constants.ISPECS_MINMAX not in ipolicy: 1090 # Nothing to check 1091 return 1092 1093 if check_std and constants.ISPECS_STD not in ipolicy: 1094 msg = "Missing key in ipolicy: %s" % constants.ISPECS_STD 1095 raise errors.ConfigurationError(msg) 1096 stdspec = ipolicy.get(constants.ISPECS_STD) 1097 if check_std: 1098 InstancePolicy._CheckIncompleteSpec(stdspec, constants.ISPECS_STD) 1099 1100 if not ipolicy[constants.ISPECS_MINMAX]: 1101 raise errors.ConfigurationError("Empty minmax specifications") 1102 std_is_good = False 1103 for minmaxspecs in ipolicy[constants.ISPECS_MINMAX]: 1104 missing = constants.ISPECS_MINMAX_KEYS - frozenset(minmaxspecs.keys()) 1105 if missing: 1106 msg = "Missing instance specification: %s" % utils.CommaJoin(missing) 1107 raise errors.ConfigurationError(msg) 1108 for (key, spec) in minmaxspecs.items(): 1109 InstancePolicy._CheckIncompleteSpec(spec, key) 1110 1111 spec_std_ok = True 1112 for param in constants.ISPECS_PARAMETERS: 1113 par_std_ok = InstancePolicy._CheckISpecParamSyntax(minmaxspecs, stdspec, 1114 param, check_std) 1115 spec_std_ok = spec_std_ok and par_std_ok 1116 std_is_good = std_is_good or spec_std_ok 1117 if not std_is_good: 1118 raise errors.ConfigurationError("Invalid std specifications")
1119 1120 @classmethod
1121 - def _CheckISpecParamSyntax(cls, minmaxspecs, stdspec, name, check_std):
1122 """Check the instance policy specs for validity on a given key. 1123 1124 We check if the instance specs makes sense for a given key, that is 1125 if minmaxspecs[min][name] <= stdspec[name] <= minmaxspec[max][name]. 1126 1127 @type minmaxspecs: dict 1128 @param minmaxspecs: dictionary with min and max instance spec 1129 @type stdspec: dict 1130 @param stdspec: dictionary with standard instance spec 1131 @type name: string 1132 @param name: what are the limits for 1133 @type check_std: bool 1134 @param check_std: Whether to check std value or just assume compliance 1135 @rtype: bool 1136 @return: C{True} when specs are valid, C{False} when standard spec for the 1137 given name is not valid 1138 @raise errors.ConfigurationError: when min/max specs for the given name 1139 are not valid 1140 1141 """ 1142 minspec = minmaxspecs[constants.ISPECS_MIN] 1143 maxspec = minmaxspecs[constants.ISPECS_MAX] 1144 min_v = minspec[name] 1145 max_v = maxspec[name] 1146 1147 if min_v > max_v: 1148 err = ("Invalid specification of min/max values for %s: %s/%s" % 1149 (name, min_v, max_v)) 1150 raise errors.ConfigurationError(err) 1151 elif check_std: 1152 std_v = stdspec.get(name, min_v) 1153 return std_v >= min_v and std_v <= max_v 1154 else: 1155 return True
1156 1157 @classmethod
1158 - def CheckDiskTemplates(cls, disk_templates):
1159 """Checks the disk templates for validity. 1160 1161 """ 1162 if not disk_templates: 1163 raise errors.ConfigurationError("Instance policy must contain" + 1164 " at least one disk template") 1165 wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES) 1166 if wrong: 1167 raise errors.ConfigurationError("Invalid disk template(s) %s" % 1168 utils.CommaJoin(wrong))
1169 1170 @classmethod
1171 - def CheckParameter(cls, key, value):
1172 """Checks a parameter. 1173 1174 Currently we expect all parameters to be float values. 1175 1176 """ 1177 try: 1178 float(value) 1179 except (TypeError, ValueError), err: 1180 raise errors.ConfigurationError("Invalid value for key" " '%s':" 1181 " '%s', error: %s" % (key, value, err))
1182
1183 1184 -def GetOSImage(osparams):
1185 """Gets the OS image value from the OS parameters. 1186 1187 @type osparams: L{dict} or NoneType 1188 @param osparams: OS parameters or None 1189 1190 @rtype: string or NoneType 1191 @return: 1192 value of OS image contained in OS parameters, or None if the OS 1193 parameters are None or the OS parameters do not contain an OS 1194 image 1195 1196 """ 1197 if osparams is None: 1198 return None 1199 else: 1200 return osparams.get("os-image", None)
1201
1202 1203 -def PutOSImage(osparams, os_image):
1204 """Update OS image value in the OS parameters 1205 1206 @type osparams: L{dict} 1207 @param osparams: OS parameters 1208 1209 @type os_image: string 1210 @param os_image: OS image 1211 1212 @rtype: NoneType 1213 @return: None 1214 1215 """ 1216 osparams["os-image"] = os_image
1217
1218 1219 -class Instance(TaggableObject):
1220 """Config object representing an instance.""" 1221 __slots__ = [ 1222 "forthcoming", 1223 "name", 1224 "primary_node", 1225 "secondary_nodes", 1226 "os", 1227 "hypervisor", 1228 "hvparams", 1229 "beparams", 1230 "osparams", 1231 "osparams_private", 1232 "admin_state", 1233 "admin_state_source", 1234 "nics", 1235 "disks", 1236 "disks_info", 1237 "disk_template", 1238 "disks_active", 1239 "network_port", 1240 "serial_no", 1241 ] + _TIMESTAMPS + _UUID 1242
1243 - def FindDisk(self, idx):
1244 """Find a disk given having a specified index. 1245 1246 This is just a wrapper that does validation of the index. 1247 1248 @type idx: int 1249 @param idx: the disk index 1250 @rtype: string 1251 @return: the corresponding disk's uuid 1252 @raise errors.OpPrereqError: when the given index is not valid 1253 1254 """ 1255 try: 1256 idx = int(idx) 1257 return self.disks[idx] 1258 except (TypeError, ValueError), err: 1259 raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err), 1260 errors.ECODE_INVAL) 1261 except IndexError: 1262 raise errors.OpPrereqError("Invalid disk index: %d (instace has disks" 1263 " 0 to %d" % (idx, len(self.disks) - 1), 1264 errors.ECODE_INVAL)
1265
1266 - def ToDict(self, _with_private=False):
1267 """Instance-specific conversion to standard python types. 1268 1269 This replaces the children lists of objects with lists of standard 1270 python types. 1271 1272 """ 1273 bo = super(Instance, self).ToDict(_with_private=_with_private) 1274 1275 if _with_private: 1276 bo["osparams_private"] = self.osparams_private.Unprivate() 1277 1278 for attr in "nics", : 1279 alist = bo.get(attr, None) 1280 if alist: 1281 nlist = outils.ContainerToDicts(alist) 1282 else: 1283 nlist = [] 1284 bo[attr] = nlist 1285 1286 if 'disk_template' in bo: 1287 del bo['disk_template'] 1288 1289 return bo
1290 1291 @classmethod
1292 - def FromDict(cls, val):
1293 """Custom function for instances. 1294 1295 """ 1296 if "admin_state" not in val: 1297 if val.get("admin_up", False): 1298 val["admin_state"] = constants.ADMINST_UP 1299 else: 1300 val["admin_state"] = constants.ADMINST_DOWN 1301 if "admin_up" in val: 1302 del val["admin_up"] 1303 obj = super(Instance, cls).FromDict(val) 1304 obj.nics = outils.ContainerFromDicts(obj.nics, list, NIC) 1305 1306 # attribute 'disks_info' is only present when deserializing from a RPC 1307 # call in the backend 1308 disks_info = getattr(obj, "disks_info", None) 1309 if disks_info: 1310 obj.disks_info = outils.ContainerFromDicts(disks_info, list, Disk) 1311 1312 return obj
1313
1314 - def UpgradeConfig(self):
1315 """Fill defaults for missing configuration values. 1316 1317 """ 1318 if self.admin_state_source is None: 1319 self.admin_state_source = constants.ADMIN_SOURCE 1320 for nic in self.nics: 1321 nic.UpgradeConfig() 1322 if self.disks is None: 1323 self.disks = [] 1324 if self.hvparams: 1325 for key in constants.HVC_GLOBALS: 1326 try: 1327 del self.hvparams[key] 1328 except KeyError: 1329 pass 1330 if self.osparams is None: 1331 self.osparams = {} 1332 if self.osparams_private is None: 1333 self.osparams_private = serializer.PrivateDict() 1334 UpgradeBeParams(self.beparams) 1335 if self.disks_active is None: 1336 self.disks_active = self.admin_state == constants.ADMINST_UP
1337
1338 1339 -class OS(ConfigObject):
1340 """Config object representing an operating system. 1341 1342 @type supported_parameters: list 1343 @ivar supported_parameters: a list of tuples, name and description, 1344 containing the supported parameters by this OS 1345 1346 @type VARIANT_DELIM: string 1347 @cvar VARIANT_DELIM: the variant delimiter 1348 1349 """ 1350 __slots__ = [ 1351 "name", 1352 "path", 1353 "api_versions", 1354 "create_script", 1355 "create_script_untrusted", 1356 "export_script", 1357 "import_script", 1358 "rename_script", 1359 "verify_script", 1360 "supported_variants", 1361 "supported_parameters", 1362 ] 1363 1364 VARIANT_DELIM = "+" 1365 1366 @classmethod
1367 - def SplitNameVariant(cls, name):
1368 """Splits the name into the proper name and variant. 1369 1370 @param name: the OS (unprocessed) name 1371 @rtype: list 1372 @return: a list of two elements; if the original name didn't 1373 contain a variant, it's returned as an empty string 1374 1375 """ 1376 nv = name.split(cls.VARIANT_DELIM, 1) 1377 if len(nv) == 1: 1378 nv.append("") 1379 return nv
1380 1381 @classmethod
1382 - def GetName(cls, name):
1383 """Returns the proper name of the os (without the variant). 1384 1385 @param name: the OS (unprocessed) name 1386 1387 """ 1388 return cls.SplitNameVariant(name)[0]
1389 1390 @classmethod
1391 - def GetVariant(cls, name):
1392 """Returns the variant the os (without the base name). 1393 1394 @param name: the OS (unprocessed) name 1395 1396 """ 1397 return cls.SplitNameVariant(name)[1]
1398
1399 - def IsTrusted(self):
1400 """Returns whether this OS is trusted. 1401 1402 @rtype: bool 1403 @return: L{True} if this OS is trusted, L{False} otherwise 1404 1405 """ 1406 return not self.create_script_untrusted
1407
1408 1409 -class ExtStorage(ConfigObject):
1410 """Config object representing an External Storage Provider. 1411 1412 """ 1413 __slots__ = [ 1414 "name", 1415 "path", 1416 "create_script", 1417 "remove_script", 1418 "grow_script", 1419 "attach_script", 1420 "detach_script", 1421 "setinfo_script", 1422 "verify_script", 1423 "snapshot_script", 1424 "open_script", 1425 "close_script", 1426 "supported_parameters", 1427 ]
1428
1429 1430 -class NodeHvState(ConfigObject):
1431 """Hypvervisor state on a node. 1432 1433 @ivar mem_total: Total amount of memory 1434 @ivar mem_node: Memory used by, or reserved for, the node itself (not always 1435 available) 1436 @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation 1437 rounding 1438 @ivar mem_inst: Memory used by instances living on node 1439 @ivar cpu_total: Total node CPU core count 1440 @ivar cpu_node: Number of CPU cores reserved for the node itself 1441 1442 """ 1443 __slots__ = [ 1444 "mem_total", 1445 "mem_node", 1446 "mem_hv", 1447 "mem_inst", 1448 "cpu_total", 1449 "cpu_node", 1450 ] + _TIMESTAMPS
1451
1452 1453 -class NodeDiskState(ConfigObject):
1454 """Disk state on a node. 1455 1456 """ 1457 __slots__ = [ 1458 "total", 1459 "reserved", 1460 "overhead", 1461 ] + _TIMESTAMPS
1462
1463 1464 -class Node(TaggableObject):
1465 """Config object representing a node. 1466 1467 @ivar hv_state: Hypervisor state (e.g. number of CPUs) 1468 @ivar hv_state_static: Hypervisor state overriden by user 1469 @ivar disk_state: Disk state (e.g. free space) 1470 @ivar disk_state_static: Disk state overriden by user 1471 1472 """ 1473 __slots__ = [ 1474 "name", 1475 "primary_ip", 1476 "secondary_ip", 1477 "serial_no", 1478 "master_candidate", 1479 "offline", 1480 "drained", 1481 "group", 1482 "master_capable", 1483 "vm_capable", 1484 "ndparams", 1485 "powered", 1486 "hv_state", 1487 "hv_state_static", 1488 "disk_state", 1489 "disk_state_static", 1490 ] + _TIMESTAMPS + _UUID 1491
1492 - def UpgradeConfig(self):
1493 """Fill defaults for missing configuration values. 1494 1495 """ 1496 # pylint: disable=E0203 1497 # because these are "defined" via slots, not manually 1498 if self.master_capable is None: 1499 self.master_capable = True 1500 1501 if self.vm_capable is None: 1502 self.vm_capable = True 1503 1504 if self.ndparams is None: 1505 self.ndparams = {} 1506 # And remove any global parameter 1507 for key in constants.NDC_GLOBALS: 1508 if key in self.ndparams: 1509 logging.warning("Ignoring %s node parameter for node %s", 1510 key, self.name) 1511 del self.ndparams[key] 1512 1513 if self.powered is None: 1514 self.powered = True 1515 1516 if self.hv_state_static is None: 1517 self.hv_state_static = {} 1518 if self.disk_state_static is None: 1519 self.disk_state_static = {}
1520
1521 - def ToDict(self, _with_private=False):
1522 """Custom function for serializing. 1523 1524 """ 1525 data = super(Node, self).ToDict(_with_private=_with_private) 1526 1527 hv_state = data.get("hv_state", None) 1528 if hv_state is not None: 1529 data["hv_state"] = outils.ContainerToDicts(hv_state) 1530 1531 disk_state = data.get("disk_state", None) 1532 if disk_state is not None: 1533 data["disk_state"] = \ 1534 dict((key, outils.ContainerToDicts(value)) 1535 for (key, value) in disk_state.items()) 1536 1537 return data
1538 1539 @classmethod
1540 - def FromDict(cls, val):
1541 """Custom function for deserializing. 1542 1543 """ 1544 obj = super(Node, cls).FromDict(val) 1545 1546 if obj.hv_state is not None: 1547 obj.hv_state = \ 1548 outils.ContainerFromDicts(obj.hv_state, dict, NodeHvState) 1549 1550 if obj.disk_state is not None: 1551 obj.disk_state = \ 1552 dict((key, outils.ContainerFromDicts(value, dict, NodeDiskState)) 1553 for (key, value) in obj.disk_state.items()) 1554 1555 return obj
1556
1557 1558 -class NodeGroup(TaggableObject):
1559 """Config object representing a node group.""" 1560 __slots__ = [ 1561 "name", 1562 "members", 1563 "ndparams", 1564 "diskparams", 1565 "ipolicy", 1566 "serial_no", 1567 "hv_state_static", 1568 "disk_state_static", 1569 "alloc_policy", 1570 "networks", 1571 ] + _TIMESTAMPS + _UUID 1572
1573 - def ToDict(self, _with_private=False):
1574 """Custom function for nodegroup. 1575 1576 This discards the members object, which gets recalculated and is only kept 1577 in memory. 1578 1579 """ 1580 mydict = super(NodeGroup, self).ToDict(_with_private=_with_private) 1581 del mydict["members"] 1582 return mydict
1583 1584 @classmethod
1585 - def FromDict(cls, val):
1586 """Custom function for nodegroup. 1587 1588 The members slot is initialized to an empty list, upon deserialization. 1589 1590 """ 1591 obj = super(NodeGroup, cls).FromDict(val) 1592 obj.members = [] 1593 return obj
1594
1595 - def UpgradeConfig(self):
1596 """Fill defaults for missing configuration values. 1597 1598 """ 1599 if self.ndparams is None: 1600 self.ndparams = {} 1601 1602 if self.serial_no is None: 1603 self.serial_no = 1 1604 1605 if self.alloc_policy is None: 1606 self.alloc_policy = constants.ALLOC_POLICY_PREFERRED 1607 1608 # We only update mtime, and not ctime, since we would not be able 1609 # to provide a correct value for creation time. 1610 if self.mtime is None: 1611 self.mtime = time.time() 1612 1613 if self.diskparams is None: 1614 self.diskparams = {} 1615 if self.ipolicy is None: 1616 self.ipolicy = MakeEmptyIPolicy() 1617 1618 if self.hv_state_static is None: 1619 self.hv_state_static = {} 1620 if self.disk_state_static is None: 1621 self.disk_state_static = {} 1622 1623 if self.networks is None: 1624 self.networks = {} 1625 1626 for network, netparams in self.networks.items(): 1627 self.networks[network] = FillDict(constants.NICC_DEFAULTS, netparams)
1628
1629 - def FillND(self, node):
1630 """Return filled out ndparams for L{objects.Node} 1631 1632 @type node: L{objects.Node} 1633 @param node: A Node object to fill 1634 @return a copy of the node's ndparams with defaults filled 1635 1636 """ 1637 return self.SimpleFillND(node.ndparams)
1638
1639 - def SimpleFillND(self, ndparams):
1640 """Fill a given ndparams dict with defaults. 1641 1642 @type ndparams: dict 1643 @param ndparams: the dict to fill 1644 @rtype: dict 1645 @return: a copy of the passed in ndparams with missing keys filled 1646 from the node group defaults 1647 1648 """ 1649 return FillDict(self.ndparams, ndparams)
1650
1651 1652 -class Cluster(TaggableObject):
1653 """Config object representing the cluster.""" 1654 __slots__ = [ 1655 "serial_no", 1656 "rsahostkeypub", 1657 "dsahostkeypub", 1658 "highest_used_port", 1659 "tcpudp_port_pool", 1660 "mac_prefix", 1661 "volume_group_name", 1662 "reserved_lvs", 1663 "drbd_usermode_helper", 1664 "default_bridge", 1665 "default_hypervisor", 1666 "master_node", 1667 "master_ip", 1668 "master_netdev", 1669 "master_netmask", 1670 "use_external_mip_script", 1671 "cluster_name", 1672 "file_storage_dir", 1673 "shared_file_storage_dir", 1674 "gluster_storage_dir", 1675 "enabled_hypervisors", 1676 "hvparams", 1677 "ipolicy", 1678 "os_hvp", 1679 "beparams", 1680 "osparams", 1681 "osparams_private_cluster", 1682 "nicparams", 1683 "ndparams", 1684 "diskparams", 1685 "candidate_pool_size", 1686 "modify_etc_hosts", 1687 "modify_ssh_setup", 1688 "maintain_node_health", 1689 "uid_pool", 1690 "default_iallocator", 1691 "default_iallocator_params", 1692 "hidden_os", 1693 "blacklisted_os", 1694 "primary_ip_family", 1695 "prealloc_wipe_disks", 1696 "hv_state_static", 1697 "disk_state_static", 1698 "enabled_disk_templates", 1699 "candidate_certs", 1700 "max_running_jobs", 1701 "max_tracked_jobs", 1702 "install_image", 1703 "instance_communication_network", 1704 "zeroing_image", 1705 "compression_tools", 1706 "enabled_user_shutdown", 1707 "data_collectors", 1708 "diagnose_data_collector_filename", 1709 "ssh_key_type", 1710 "ssh_key_bits", 1711 ] + _TIMESTAMPS + _UUID 1712
1713 - def UpgradeConfig(self):
1714 """Fill defaults for missing configuration values. 1715 1716 """ 1717 # pylint: disable=E0203 1718 # because these are "defined" via slots, not manually 1719 if self.hvparams is None: 1720 self.hvparams = constants.HVC_DEFAULTS 1721 else: 1722 for hypervisor in constants.HYPER_TYPES: 1723 try: 1724 existing_params = self.hvparams[hypervisor] 1725 except KeyError: 1726 existing_params = {} 1727 self.hvparams[hypervisor] = FillDict( 1728 constants.HVC_DEFAULTS[hypervisor], existing_params) 1729 1730 if self.os_hvp is None: 1731 self.os_hvp = {} 1732 1733 if self.osparams is None: 1734 self.osparams = {} 1735 # osparams_private_cluster added in 2.12 1736 if self.osparams_private_cluster is None: 1737 self.osparams_private_cluster = {} 1738 1739 self.ndparams = UpgradeNDParams(self.ndparams) 1740 1741 self.beparams = UpgradeGroupedParams(self.beparams, 1742 constants.BEC_DEFAULTS) 1743 for beparams_group in self.beparams: 1744 UpgradeBeParams(self.beparams[beparams_group]) 1745 1746 migrate_default_bridge = not self.nicparams 1747 self.nicparams = UpgradeGroupedParams(self.nicparams, 1748 constants.NICC_DEFAULTS) 1749 if migrate_default_bridge: 1750 self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \ 1751 self.default_bridge 1752 1753 if self.modify_etc_hosts is None: 1754 self.modify_etc_hosts = True 1755 1756 if self.modify_ssh_setup is None: 1757 self.modify_ssh_setup = True 1758 1759 # default_bridge is no longer used in 2.1. The slot is left there to 1760 # support auto-upgrading. It can be removed once we decide to deprecate 1761 # upgrading straight from 2.0. 1762 if self.default_bridge is not None: 1763 self.default_bridge = None 1764 1765 # default_hypervisor is just the first enabled one in 2.1. This slot and 1766 # code can be removed once upgrading straight from 2.0 is deprecated. 1767 if self.default_hypervisor is not None: 1768 self.enabled_hypervisors = ([self.default_hypervisor] + 1769 [hvname for hvname in self.enabled_hypervisors 1770 if hvname != self.default_hypervisor]) 1771 self.default_hypervisor = None 1772 1773 # maintain_node_health added after 2.1.1 1774 if self.maintain_node_health is None: 1775 self.maintain_node_health = False 1776 1777 if self.uid_pool is None: 1778 self.uid_pool = [] 1779 1780 if self.default_iallocator is None: 1781 self.default_iallocator = "" 1782 1783 if self.default_iallocator_params is None: 1784 self.default_iallocator_params = {} 1785 1786 # reserved_lvs added before 2.2 1787 if self.reserved_lvs is None: 1788 self.reserved_lvs = [] 1789 1790 # hidden and blacklisted operating systems added before 2.2.1 1791 if self.hidden_os is None: 1792 self.hidden_os = [] 1793 1794 if self.blacklisted_os is None: 1795 self.blacklisted_os = [] 1796 1797 # primary_ip_family added before 2.3 1798 if self.primary_ip_family is None: 1799 self.primary_ip_family = AF_INET 1800 1801 if self.master_netmask is None: 1802 ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family) 1803 self.master_netmask = ipcls.iplen 1804 1805 if self.prealloc_wipe_disks is None: 1806 self.prealloc_wipe_disks = False 1807 1808 # shared_file_storage_dir added before 2.5 1809 if self.shared_file_storage_dir is None: 1810 self.shared_file_storage_dir = "" 1811 1812 # gluster_storage_dir added in 2.11 1813 if self.gluster_storage_dir is None: 1814 self.gluster_storage_dir = "" 1815 1816 if self.use_external_mip_script is None: 1817 self.use_external_mip_script = False 1818 1819 if self.diskparams: 1820 self.diskparams = UpgradeDiskParams(self.diskparams) 1821 else: 1822 self.diskparams = constants.DISK_DT_DEFAULTS.copy() 1823 1824 # instance policy added before 2.6 1825 if self.ipolicy is None: 1826 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {}) 1827 else: 1828 # we can either make sure to upgrade the ipolicy always, or only 1829 # do it in some corner cases (e.g. missing keys); note that this 1830 # will break any removal of keys from the ipolicy dict 1831 wrongkeys = frozenset(self.ipolicy.keys()) - constants.IPOLICY_ALL_KEYS 1832 if wrongkeys: 1833 # These keys would be silently removed by FillIPolicy() 1834 msg = ("Cluster instance policy contains spurious keys: %s" % 1835 utils.CommaJoin(wrongkeys)) 1836 raise errors.ConfigurationError(msg) 1837 self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy) 1838 1839 # hv_state_static added in 2.7 1840 if self.hv_state_static is None: 1841 self.hv_state_static = {} 1842 if self.disk_state_static is None: 1843 self.disk_state_static = {} 1844 1845 if self.candidate_certs is None: 1846 self.candidate_certs = {} 1847 1848 if self.max_running_jobs is None: 1849 self.max_running_jobs = constants.LUXID_MAXIMAL_RUNNING_JOBS_DEFAULT 1850 1851 if self.max_tracked_jobs is None: 1852 self.max_tracked_jobs = constants.LUXID_MAXIMAL_TRACKED_JOBS_DEFAULT 1853 1854 if self.instance_communication_network is None: 1855 self.instance_communication_network = "" 1856 1857 if self.install_image is None: 1858 self.install_image = "" 1859 1860 if self.compression_tools is None: 1861 self.compression_tools = constants.IEC_DEFAULT_TOOLS 1862 1863 if self.enabled_user_shutdown is None: 1864 self.enabled_user_shutdown = False 1865 1866 if self.ssh_key_type is None: 1867 self.ssh_key_type = constants.SSH_DEFAULT_KEY_TYPE 1868 1869 if self.ssh_key_bits is None: 1870 self.ssh_key_bits = constants.SSH_DEFAULT_KEY_BITS
1871 1872 @property
1873 - def primary_hypervisor(self):
1874 """The first hypervisor is the primary. 1875 1876 Useful, for example, for L{Node}'s hv/disk state. 1877 1878 """ 1879 return self.enabled_hypervisors[0]
1880
1881 - def ToDict(self, _with_private=False):
1882 """Custom function for cluster. 1883 1884 """ 1885 mydict = super(Cluster, self).ToDict(_with_private=_with_private) 1886 1887 # Explicitly save private parameters. 1888 if _with_private: 1889 for os in mydict["osparams_private_cluster"]: 1890 mydict["osparams_private_cluster"][os] = \ 1891 self.osparams_private_cluster[os].Unprivate() 1892 1893 if self.tcpudp_port_pool is None: 1894 tcpudp_port_pool = [] 1895 else: 1896 tcpudp_port_pool = list(self.tcpudp_port_pool) 1897 1898 mydict["tcpudp_port_pool"] = tcpudp_port_pool 1899 1900 return mydict
1901 1902 @classmethod
1903 - def FromDict(cls, val):
1904 """Custom function for cluster. 1905 1906 """ 1907 obj = super(Cluster, cls).FromDict(val) 1908 1909 if obj.tcpudp_port_pool is None: 1910 obj.tcpudp_port_pool = set() 1911 elif not isinstance(obj.tcpudp_port_pool, set): 1912 obj.tcpudp_port_pool = set(obj.tcpudp_port_pool) 1913 1914 return obj
1915
1916 - def SimpleFillDP(self, diskparams):
1917 """Fill a given diskparams dict with cluster defaults. 1918 1919 @param diskparams: The diskparams 1920 @return: The defaults dict 1921 1922 """ 1923 return FillDiskParams(self.diskparams, diskparams)
1924
1925 - def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1926 """Get the default hypervisor parameters for the cluster. 1927 1928 @param hypervisor: the hypervisor name 1929 @param os_name: if specified, we'll also update the defaults for this OS 1930 @param skip_keys: if passed, list of keys not to use 1931 @return: the defaults dict 1932 1933 """ 1934 if skip_keys is None: 1935 skip_keys = [] 1936 1937 fill_stack = [self.hvparams.get(hypervisor, {})] 1938 if os_name is not None: 1939 os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {}) 1940 fill_stack.append(os_hvp) 1941 1942 ret_dict = {} 1943 for o_dict in fill_stack: 1944 ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys) 1945 1946 return ret_dict
1947
1948 - def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1949 """Fill a given hvparams dict with cluster defaults. 1950 1951 @type hv_name: string 1952 @param hv_name: the hypervisor to use 1953 @type os_name: string 1954 @param os_name: the OS to use for overriding the hypervisor defaults 1955 @type skip_globals: boolean 1956 @param skip_globals: if True, the global hypervisor parameters will 1957 not be filled 1958 @rtype: dict 1959 @return: a copy of the given hvparams with missing keys filled from 1960 the cluster defaults 1961 1962 """ 1963 if skip_globals: 1964 skip_keys = constants.HVC_GLOBALS 1965 else: 1966 skip_keys = [] 1967 1968 def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys) 1969 return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1970
1971 - def FillHV(self, instance, skip_globals=False):
1972 """Fill an instance's hvparams dict with cluster defaults. 1973 1974 @type instance: L{objects.Instance} 1975 @param instance: the instance parameter to fill 1976 @type skip_globals: boolean 1977 @param skip_globals: if True, the global hypervisor parameters will 1978 not be filled 1979 @rtype: dict 1980 @return: a copy of the instance's hvparams with missing keys filled from 1981 the cluster defaults 1982 1983 """ 1984 return self.SimpleFillHV(instance.hypervisor, instance.os, 1985 instance.hvparams, skip_globals)
1986
1987 - def SimpleFillBE(self, beparams):
1988 """Fill a given beparams dict with cluster defaults. 1989 1990 @type beparams: dict 1991 @param beparams: the dict to fill 1992 @rtype: dict 1993 @return: a copy of the passed in beparams with missing keys filled 1994 from the cluster defaults 1995 1996 """ 1997 return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1998
1999 - def FillBE(self, instance):
2000 """Fill an instance's beparams dict with cluster defaults. 2001 2002 @type instance: L{objects.Instance} 2003 @param instance: the instance parameter to fill 2004 @rtype: dict 2005 @return: a copy of the instance's beparams with missing keys filled from 2006 the cluster defaults 2007 2008 """ 2009 return self.SimpleFillBE(instance.beparams)
2010
2011 - def SimpleFillNIC(self, nicparams):
2012 """Fill a given nicparams dict with cluster defaults. 2013 2014 @type nicparams: dict 2015 @param nicparams: the dict to fill 2016 @rtype: dict 2017 @return: a copy of the passed in nicparams with missing keys filled 2018 from the cluster defaults 2019 2020 """ 2021 return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
2022
2023 - def SimpleFillOS(self, os_name, 2024 os_params_public, 2025 os_params_private=None, 2026 os_params_secret=None):
2027 """Fill an instance's osparams dict with cluster defaults. 2028 2029 @type os_name: string 2030 @param os_name: the OS name to use 2031 @type os_params_public: dict 2032 @param os_params_public: the dict to fill with default values 2033 @type os_params_private: dict 2034 @param os_params_private: the dict with private fields to fill 2035 with default values. Not passing this field 2036 results in no private fields being added to the 2037 return value. Private fields will be wrapped in 2038 L{Private} objects. 2039 @type os_params_secret: dict 2040 @param os_params_secret: the dict with secret fields to fill 2041 with default values. Not passing this field 2042 results in no secret fields being added to the 2043 return value. Private fields will be wrapped in 2044 L{Private} objects. 2045 @rtype: dict 2046 @return: a copy of the instance's osparams with missing keys filled from 2047 the cluster defaults. Private and secret parameters are not included 2048 unless the respective optional parameters are supplied. 2049 2050 """ 2051 if os_name is None: 2052 name_only = None 2053 else: 2054 name_only = OS.GetName(os_name) 2055 2056 defaults_base_public = self.osparams.get(name_only, {}) 2057 defaults_public = FillDict(defaults_base_public, 2058 self.osparams.get(os_name, {})) 2059 params_public = FillDict(defaults_public, os_params_public) 2060 2061 if os_params_private is not None: 2062 defaults_base_private = self.osparams_private_cluster.get(name_only, {}) 2063 defaults_private = FillDict(defaults_base_private, 2064 self.osparams_private_cluster.get(os_name, 2065 {})) 2066 params_private = FillDict(defaults_private, os_params_private) 2067 else: 2068 params_private = {} 2069 2070 if os_params_secret is not None: 2071 # There can't be default secret settings, so there's nothing to be done. 2072 params_secret = os_params_secret 2073 else: 2074 params_secret = {} 2075 2076 # Enforce that the set of keys be distinct: 2077 duplicate_keys = utils.GetRepeatedKeys(params_public, 2078 params_private, 2079 params_secret) 2080 if not duplicate_keys: 2081 2082 # Actually update them: 2083 params_public.update(params_private) 2084 params_public.update(params_secret) 2085 2086 return params_public 2087 2088 else: 2089 2090 def formatter(keys): 2091 return utils.CommaJoin(sorted(map(repr, keys))) if keys else "(none)"
2092 2093 #Lose the values. 2094 params_public = set(params_public) 2095 params_private = set(params_private) 2096 params_secret = set(params_secret) 2097 2098 msg = """Cannot assign multiple values to OS parameters. 2099 2100 Conflicting OS parameters that would have been set by this operation: 2101 - at public visibility: {public} 2102 - at private visibility: {private} 2103 - at secret visibility: {secret} 2104 """.format(dupes=formatter(duplicate_keys), 2105 public=formatter(params_public & duplicate_keys), 2106 private=formatter(params_private & duplicate_keys), 2107 secret=formatter(params_secret & duplicate_keys)) 2108 raise errors.OpPrereqError(msg)
2109 2110 @staticmethod
2111 - def SimpleFillHvState(hv_state):
2112 """Fill an hv_state sub dict with cluster defaults. 2113 2114 """ 2115 return FillDict(constants.HVST_DEFAULTS, hv_state)
2116 2117 @staticmethod
2118 - def SimpleFillDiskState(disk_state):
2119 """Fill an disk_state sub dict with cluster defaults. 2120 2121 """ 2122 return FillDict(constants.DS_DEFAULTS, disk_state)
2123
2124 - def FillND(self, node, nodegroup):
2125 """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node} 2126 2127 @type node: L{objects.Node} 2128 @param node: A Node object to fill 2129 @type nodegroup: L{objects.NodeGroup} 2130 @param nodegroup: A Node object to fill 2131 @return a copy of the node's ndparams with defaults filled 2132 2133 """ 2134 return self.SimpleFillND(nodegroup.FillND(node))
2135
2136 - def FillNDGroup(self, nodegroup):
2137 """Return filled out ndparams for just L{objects.NodeGroup} 2138 2139 @type nodegroup: L{objects.NodeGroup} 2140 @param nodegroup: A Node object to fill 2141 @return a copy of the node group's ndparams with defaults filled 2142 2143 """ 2144 return self.SimpleFillND(nodegroup.SimpleFillND({}))
2145
2146 - def SimpleFillND(self, ndparams):
2147 """Fill a given ndparams dict with defaults. 2148 2149 @type ndparams: dict 2150 @param ndparams: the dict to fill 2151 @rtype: dict 2152 @return: a copy of the passed in ndparams with missing keys filled 2153 from the cluster defaults 2154 2155 """ 2156 return FillDict(self.ndparams, ndparams)
2157
2158 - def SimpleFillIPolicy(self, ipolicy):
2159 """ Fill instance policy dict with defaults. 2160 2161 @type ipolicy: dict 2162 @param ipolicy: the dict to fill 2163 @rtype: dict 2164 @return: a copy of passed ipolicy with missing keys filled from 2165 the cluster defaults 2166 2167 """ 2168 return FillIPolicy(self.ipolicy, ipolicy)
2169
2170 - def IsDiskTemplateEnabled(self, disk_template):
2171 """Checks if a particular disk template is enabled. 2172 2173 """ 2174 return utils.storage.IsDiskTemplateEnabled( 2175 disk_template, self.enabled_disk_templates)
2176
2177 - def IsFileStorageEnabled(self):
2178 """Checks if file storage is enabled. 2179 2180 """ 2181 return utils.storage.IsFileStorageEnabled(self.enabled_disk_templates)
2182
2183 - def IsSharedFileStorageEnabled(self):
2184 """Checks if shared file storage is enabled. 2185 2186 """ 2187 return utils.storage.IsSharedFileStorageEnabled( 2188 self.enabled_disk_templates)
2189
2190 2191 -class BlockDevStatus(ConfigObject):
2192 """Config object representing the status of a block device.""" 2193 __slots__ = [ 2194 "dev_path", 2195 "major", 2196 "minor", 2197 "sync_percent", 2198 "estimated_time", 2199 "is_degraded", 2200 "ldisk_status", 2201 ]
2202
2203 2204 -class ImportExportStatus(ConfigObject):
2205 """Config object representing the status of an import or export.""" 2206 __slots__ = [ 2207 "recent_output", 2208 "listen_port", 2209 "connected", 2210 "progress_mbytes", 2211 "progress_throughput", 2212 "progress_eta", 2213 "progress_percent", 2214 "exit_status", 2215 "error_message", 2216 ] + _TIMESTAMPS
2217
2218 2219 -class ImportExportOptions(ConfigObject):
2220 """Options for import/export daemon 2221 2222 @ivar key_name: X509 key name (None for cluster certificate) 2223 @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate) 2224 @ivar compress: Compression tool to use 2225 @ivar magic: Used to ensure the connection goes to the right disk 2226 @ivar ipv6: Whether to use IPv6 2227 @ivar connect_timeout: Number of seconds for establishing connection 2228 2229 """ 2230 __slots__ = [ 2231 "key_name", 2232 "ca_pem", 2233 "compress", 2234 "magic", 2235 "ipv6", 2236 "connect_timeout", 2237 ]
2238
2239 2240 -class ConfdRequest(ConfigObject):
2241 """Object holding a confd request. 2242 2243 @ivar protocol: confd protocol version 2244 @ivar type: confd query type 2245 @ivar query: query request 2246 @ivar rsalt: requested reply salt 2247 2248 """ 2249 __slots__ = [ 2250 "protocol", 2251 "type", 2252 "query", 2253 "rsalt", 2254 ]
2255
2256 2257 -class ConfdReply(ConfigObject):
2258 """Object holding a confd reply. 2259 2260 @ivar protocol: confd protocol version 2261 @ivar status: reply status code (ok, error) 2262 @ivar answer: confd query reply 2263 @ivar serial: configuration serial number 2264 2265 """ 2266 __slots__ = [ 2267 "protocol", 2268 "status", 2269 "answer", 2270 "serial", 2271 ]
2272
2273 2274 -class QueryFieldDefinition(ConfigObject):
2275 """Object holding a query field definition. 2276 2277 @ivar name: Field name 2278 @ivar title: Human-readable title 2279 @ivar kind: Field type 2280 @ivar doc: Human-readable description 2281 2282 """ 2283 __slots__ = [ 2284 "name", 2285 "title", 2286 "kind", 2287 "doc", 2288 ]
2289
2290 2291 -class _QueryResponseBase(ConfigObject):
2292 __slots__ = [ 2293 "fields", 2294 ] 2295
2296 - def ToDict(self, _with_private=False):
2297 """Custom function for serializing. 2298 2299 """ 2300 mydict = super(_QueryResponseBase, self).ToDict() 2301 mydict["fields"] = outils.ContainerToDicts(mydict["fields"]) 2302 return mydict
2303 2304 @classmethod
2305 - def FromDict(cls, val):
2306 """Custom function for de-serializing. 2307 2308 """ 2309 obj = super(_QueryResponseBase, cls).FromDict(val) 2310 obj.fields = \ 2311 outils.ContainerFromDicts(obj.fields, list, QueryFieldDefinition) 2312 return obj
2313
2314 2315 -class QueryResponse(_QueryResponseBase):
2316 """Object holding the response to a query. 2317 2318 @ivar fields: List of L{QueryFieldDefinition} objects 2319 @ivar data: Requested data 2320 2321 """ 2322 __slots__ = [ 2323 "data", 2324 ]
2325
2326 2327 -class QueryFieldsRequest(ConfigObject):
2328 """Object holding a request for querying available fields. 2329 2330 """ 2331 __slots__ = [ 2332 "what", 2333 "fields", 2334 ]
2335
2336 2337 -class QueryFieldsResponse(_QueryResponseBase):
2338 """Object holding the response to a query for fields. 2339 2340 @ivar fields: List of L{QueryFieldDefinition} objects 2341 2342 """ 2343 __slots__ = []
2344
2345 2346 -class MigrationStatus(ConfigObject):
2347 """Object holding the status of a migration. 2348 2349 """ 2350 __slots__ = [ 2351 "status", 2352 "transferred_ram", 2353 "total_ram", 2354 ]
2355
2356 2357 -class InstanceConsole(ConfigObject):
2358 """Object describing how to access the console of an instance. 2359 2360 """ 2361 __slots__ = [ 2362 "instance", 2363 "kind", 2364 "message", 2365 "host", 2366 "port", 2367 "user", 2368 "command", 2369 "display", 2370 ] 2371
2372 - def Validate(self):
2373 """Validates contents of this object. 2374 2375 """ 2376 assert self.kind in constants.CONS_ALL, "Unknown console type" 2377 assert self.instance, "Missing instance name" 2378 assert self.message or self.kind in [constants.CONS_SSH, 2379 constants.CONS_SPICE, 2380 constants.CONS_VNC] 2381 assert self.host or self.kind == constants.CONS_MESSAGE 2382 assert self.port or self.kind in [constants.CONS_MESSAGE, 2383 constants.CONS_SSH] 2384 assert self.user or self.kind in [constants.CONS_MESSAGE, 2385 constants.CONS_SPICE, 2386 constants.CONS_VNC] 2387 assert self.command or self.kind in [constants.CONS_MESSAGE, 2388 constants.CONS_SPICE, 2389 constants.CONS_VNC] 2390 assert self.display or self.kind in [constants.CONS_MESSAGE, 2391 constants.CONS_SPICE, 2392 constants.CONS_SSH]
2393
2394 2395 -class Network(TaggableObject):
2396 """Object representing a network definition for ganeti. 2397 2398 """ 2399 __slots__ = [ 2400 "name", 2401 "serial_no", 2402 "mac_prefix", 2403 "network", 2404 "network6", 2405 "gateway", 2406 "gateway6", 2407 "reservations", 2408 "ext_reservations", 2409 ] + _TIMESTAMPS + _UUID 2410
2411 - def HooksDict(self, prefix=""):
2412 """Export a dictionary used by hooks with a network's information. 2413 2414 @type prefix: String 2415 @param prefix: Prefix to prepend to the dict entries 2416 2417 """ 2418 result = { 2419 "%sNETWORK_NAME" % prefix: self.name, 2420 "%sNETWORK_UUID" % prefix: self.uuid, 2421 "%sNETWORK_TAGS" % prefix: " ".join(self.GetTags()), 2422 } 2423 if self.network: 2424 result["%sNETWORK_SUBNET" % prefix] = self.network 2425 if self.gateway: 2426 result["%sNETWORK_GATEWAY" % prefix] = self.gateway 2427 if self.network6: 2428 result["%sNETWORK_SUBNET6" % prefix] = self.network6 2429 if self.gateway6: 2430 result["%sNETWORK_GATEWAY6" % prefix] = self.gateway6 2431 if self.mac_prefix: 2432 result["%sNETWORK_MAC_PREFIX" % prefix] = self.mac_prefix 2433 2434 return result
2435 2436 @classmethod
2437 - def FromDict(cls, val):
2438 """Custom function for networks. 2439 2440 Remove deprecated network_type and family. 2441 2442 """ 2443 if "network_type" in val: 2444 del val["network_type"] 2445 if "family" in val: 2446 del val["family"] 2447 obj = super(Network, cls).FromDict(val) 2448 return obj
2449
2450 2451 # need to inherit object in order to use super() 2452 -class SerializableConfigParser(ConfigParser.SafeConfigParser, object):
2453 """Simple wrapper over ConfigParse that allows serialization. 2454 2455 This class is basically ConfigParser.SafeConfigParser with two 2456 additional methods that allow it to serialize/unserialize to/from a 2457 buffer. 2458 2459 """
2460 - def Dumps(self):
2461 """Dump this instance and return the string representation.""" 2462 buf = StringIO() 2463 self.write(buf) 2464 return buf.getvalue()
2465 2466 @classmethod
2467 - def Loads(cls, data):
2468 """Load data from a string.""" 2469 buf = StringIO(data) 2470 cfp = cls() 2471 cfp.readfp(buf) 2472 return cfp
2473
2474 - def get(self, section, option, **kwargs):
2475 value = None 2476 try: 2477 value = super(SerializableConfigParser, self).get(section, option, 2478 **kwargs) 2479 if value.lower() == constants.VALUE_NONE: 2480 value = None 2481 except ConfigParser.NoOptionError: 2482 r = re.compile(r"(disk|nic)\d+_name|nic\d+_(network|vlan)") 2483 match = r.match(option) 2484 if match: 2485 pass 2486 else: 2487 raise 2488 2489 return value
2490
2491 2492 -class LvmPvInfo(ConfigObject):
2493 """Information about an LVM physical volume (PV). 2494 2495 @type name: string 2496 @ivar name: name of the PV 2497 @type vg_name: string 2498 @ivar vg_name: name of the volume group containing the PV 2499 @type size: float 2500 @ivar size: size of the PV in MiB 2501 @type free: float 2502 @ivar free: free space in the PV, in MiB 2503 @type attributes: string 2504 @ivar attributes: PV attributes 2505 @type lv_list: list of strings 2506 @ivar lv_list: names of the LVs hosted on the PV 2507 """ 2508 __slots__ = [ 2509 "name", 2510 "vg_name", 2511 "size", 2512 "free", 2513 "attributes", 2514 "lv_list" 2515 ] 2516
2517 - def IsEmpty(self):
2518 """Is this PV empty? 2519 2520 """ 2521 return self.size <= (self.free + 1)
2522
2523 - def IsAllocatable(self):
2524 """Is this PV allocatable? 2525 2526 """ 2527 return ("a" in self.attributes)
2528