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