Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 918eb80b

History | View | Annotate | Download (51.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Transportable objects for Ganeti.
23

24
This module provides small, mostly data-only objects which are safe to
25
pass to and from external parties.
26

27
"""
28

    
29
# pylint: disable=E0203,W0201,R0902
30

    
31
# E0203: Access to member %r before its definition, since we use
32
# objects.py which doesn't explicitely initialise its members
33

    
34
# W0201: Attribute '%s' defined outside __init__
35

    
36
# R0902: Allow instances of these objects to have more than 20 attributes
37

    
38
import ConfigParser
39
import re
40
import copy
41
import time
42
from cStringIO import StringIO
43

    
44
from ganeti import errors
45
from ganeti import constants
46
from ganeti import netutils
47

    
48
from socket import AF_INET
49

    
50

    
51
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
52
           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
53

    
54
_TIMESTAMPS = ["ctime", "mtime"]
55
_UUID = ["uuid"]
56

    
57

    
58
def FillDict(defaults_dict, custom_dict, skip_keys=None):
59
  """Basic function to apply settings on top a default dict.
60

61
  @type defaults_dict: dict
62
  @param defaults_dict: dictionary holding the default values
63
  @type custom_dict: dict
64
  @param custom_dict: dictionary holding customized value
65
  @type skip_keys: list
66
  @param skip_keys: which keys not to fill
67
  @rtype: dict
68
  @return: dict with the 'full' values
69

70
  """
71
  ret_dict = copy.deepcopy(defaults_dict)
72
  ret_dict.update(custom_dict)
73
  if skip_keys:
74
    for k in skip_keys:
75
      try:
76
        del ret_dict[k]
77
      except KeyError:
78
        pass
79
  return ret_dict
80

    
81

    
82
def FillDictOfDicts(defaults_dict, custom_dict, skip_keys=None):
83
  """Run FillDict for each key in dictionary.
84

85
  """
86
  ret_dict = {}
87
  for key in defaults_dict.keys():
88
    ret_dict[key] = FillDict(defaults_dict[key],
89
                             custom_dict.get(key, {}),
90
                             skip_keys=skip_keys)
91
  return ret_dict
92

    
93

    
94
def UpgradeGroupedParams(target, defaults):
95
  """Update all groups for the target parameter.
96

97
  @type target: dict of dicts
98
  @param target: {group: {parameter: value}}
99
  @type defaults: dict
100
  @param defaults: default parameter values
101

102
  """
103
  if target is None:
104
    target = {constants.PP_DEFAULT: defaults}
105
  else:
106
    for group in target:
107
      target[group] = FillDict(defaults, target[group])
108
  return target
109

    
110

    
111
def UpgradeBeParams(target):
112
  """Update the be parameters dict to the new format.
113

114
  @type target: dict
115
  @param target: "be" parameters dict
116

117
  """
118
  if constants.BE_MEMORY in target:
119
    memory = target[constants.BE_MEMORY]
120
    target[constants.BE_MAXMEM] = memory
121
    target[constants.BE_MINMEM] = memory
122
    del target[constants.BE_MEMORY]
123

    
124

    
125
def UpgradeDiskParams(diskparams):
126
  """Upgrade the disk parameters.
127

128
  @type diskparams: dict
129
  @param diskparams: disk parameters to upgrade
130
  @rtype: dict
131
  @return: the upgraded disk parameters dit
132

133
  """
134
  result = dict()
135
  if diskparams is None:
136
    result = constants.DISK_DT_DEFAULTS.copy()
137
  else:
138
    # Update the disk parameter values for each disk template.
139
    # The code iterates over constants.DISK_TEMPLATES because new templates
140
    # might have been added.
141
    for template in constants.DISK_TEMPLATES:
142
      if template not in diskparams:
143
        result[template] = constants.DISK_DT_DEFAULTS[template].copy()
144
      else:
145
        result[template] = FillDict(constants.DISK_DT_DEFAULTS[template],
146
                                    diskparams[template])
147

    
148
  return result
149

    
150

    
151
def MakeEmptyIPolicy():
152
  """Create empty IPolicy dictionary.
153

154
  """
155
  return dict([
156
    (constants.MIN_ISPECS, dict()),
157
    (constants.MAX_ISPECS, dict()),
158
    (constants.STD_ISPECS, dict()),
159
    ])
160

    
161

    
162
class ConfigObject(object):
163
  """A generic config object.
164

165
  It has the following properties:
166

167
    - provides somewhat safe recursive unpickling and pickling for its classes
168
    - unset attributes which are defined in slots are always returned
169
      as None instead of raising an error
170

171
  Classes derived from this must always declare __slots__ (we use many
172
  config objects and the memory reduction is useful)
173

174
  """
175
  __slots__ = []
176

    
177
  def __init__(self, **kwargs):
178
    for k, v in kwargs.iteritems():
179
      setattr(self, k, v)
180

    
181
  def __getattr__(self, name):
182
    if name not in self._all_slots():
183
      raise AttributeError("Invalid object attribute %s.%s" %
184
                           (type(self).__name__, name))
185
    return None
186

    
187
  def __setstate__(self, state):
188
    slots = self._all_slots()
189
    for name in state:
190
      if name in slots:
191
        setattr(self, name, state[name])
192

    
193
  @classmethod
194
  def _all_slots(cls):
195
    """Compute the list of all declared slots for a class.
196

197
    """
198
    slots = []
199
    for parent in cls.__mro__:
200
      slots.extend(getattr(parent, "__slots__", []))
201
    return slots
202

    
203
  def ToDict(self):
204
    """Convert to a dict holding only standard python types.
205

206
    The generic routine just dumps all of this object's attributes in
207
    a dict. It does not work if the class has children who are
208
    ConfigObjects themselves (e.g. the nics list in an Instance), in
209
    which case the object should subclass the function in order to
210
    make sure all objects returned are only standard python types.
211

212
    """
213
    result = {}
214
    for name in self._all_slots():
215
      value = getattr(self, name, None)
216
      if value is not None:
217
        result[name] = value
218
    return result
219

    
220
  __getstate__ = ToDict
221

    
222
  @classmethod
223
  def FromDict(cls, val):
224
    """Create an object from a dictionary.
225

226
    This generic routine takes a dict, instantiates a new instance of
227
    the given class, and sets attributes based on the dict content.
228

229
    As for `ToDict`, this does not work if the class has children
230
    who are ConfigObjects themselves (e.g. the nics list in an
231
    Instance), in which case the object should subclass the function
232
    and alter the objects.
233

234
    """
235
    if not isinstance(val, dict):
236
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
237
                                      " expected dict, got %s" % type(val))
238
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
239
    obj = cls(**val_str) # pylint: disable=W0142
240
    return obj
241

    
242
  @staticmethod
243
  def _ContainerToDicts(container):
244
    """Convert the elements of a container to standard python types.
245

246
    This method converts a container with elements derived from
247
    ConfigData to standard python types. If the container is a dict,
248
    we don't touch the keys, only the values.
249

250
    """
251
    if isinstance(container, dict):
252
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
253
    elif isinstance(container, (list, tuple, set, frozenset)):
254
      ret = [elem.ToDict() for elem in container]
255
    else:
256
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
257
                      type(container))
258
    return ret
259

    
260
  @staticmethod
261
  def _ContainerFromDicts(source, c_type, e_type):
262
    """Convert a container from standard python types.
263

264
    This method converts a container with standard python types to
265
    ConfigData objects. If the container is a dict, we don't touch the
266
    keys, only the values.
267

268
    """
269
    if not isinstance(c_type, type):
270
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
271
                      " not a type" % type(c_type))
272
    if source is None:
273
      source = c_type()
274
    if c_type is dict:
275
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
276
    elif c_type in (list, tuple, set, frozenset):
277
      ret = c_type([e_type.FromDict(elem) for elem in source])
278
    else:
279
      raise TypeError("Invalid container type %s passed to"
280
                      " _ContainerFromDicts" % c_type)
281
    return ret
282

    
283
  def Copy(self):
284
    """Makes a deep copy of the current object and its children.
285

286
    """
287
    dict_form = self.ToDict()
288
    clone_obj = self.__class__.FromDict(dict_form)
289
    return clone_obj
290

    
291
  def __repr__(self):
292
    """Implement __repr__ for ConfigObjects."""
293
    return repr(self.ToDict())
294

    
295
  def UpgradeConfig(self):
296
    """Fill defaults for missing configuration values.
297

298
    This method will be called at configuration load time, and its
299
    implementation will be object dependent.
300

301
    """
302
    pass
303

    
304

    
305
class TaggableObject(ConfigObject):
306
  """An generic class supporting tags.
307

308
  """
309
  __slots__ = ["tags"]
310
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
311

    
312
  @classmethod
313
  def ValidateTag(cls, tag):
314
    """Check if a tag is valid.
315

316
    If the tag is invalid, an errors.TagError will be raised. The
317
    function has no return value.
318

319
    """
320
    if not isinstance(tag, basestring):
321
      raise errors.TagError("Invalid tag type (not a string)")
322
    if len(tag) > constants.MAX_TAG_LEN:
323
      raise errors.TagError("Tag too long (>%d characters)" %
324
                            constants.MAX_TAG_LEN)
325
    if not tag:
326
      raise errors.TagError("Tags cannot be empty")
327
    if not cls.VALID_TAG_RE.match(tag):
328
      raise errors.TagError("Tag contains invalid characters")
329

    
330
  def GetTags(self):
331
    """Return the tags list.
332

333
    """
334
    tags = getattr(self, "tags", None)
335
    if tags is None:
336
      tags = self.tags = set()
337
    return tags
338

    
339
  def AddTag(self, tag):
340
    """Add a new tag.
341

342
    """
343
    self.ValidateTag(tag)
344
    tags = self.GetTags()
345
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
346
      raise errors.TagError("Too many tags")
347
    self.GetTags().add(tag)
348

    
349
  def RemoveTag(self, tag):
350
    """Remove a tag.
351

352
    """
353
    self.ValidateTag(tag)
354
    tags = self.GetTags()
355
    try:
356
      tags.remove(tag)
357
    except KeyError:
358
      raise errors.TagError("Tag not found")
359

    
360
  def ToDict(self):
361
    """Taggable-object-specific conversion to standard python types.
362

363
    This replaces the tags set with a list.
364

365
    """
366
    bo = super(TaggableObject, self).ToDict()
367

    
368
    tags = bo.get("tags", None)
369
    if isinstance(tags, set):
370
      bo["tags"] = list(tags)
371
    return bo
372

    
373
  @classmethod
374
  def FromDict(cls, val):
375
    """Custom function for instances.
376

377
    """
378
    obj = super(TaggableObject, cls).FromDict(val)
379
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
380
      obj.tags = set(obj.tags)
381
    return obj
382

    
383

    
384
class MasterNetworkParameters(ConfigObject):
385
  """Network configuration parameters for the master
386

387
  @ivar name: master name
388
  @ivar ip: master IP
389
  @ivar netmask: master netmask
390
  @ivar netdev: master network device
391
  @ivar ip_family: master IP family
392

393
  """
394
  __slots__ = [
395
    "name",
396
    "ip",
397
    "netmask",
398
    "netdev",
399
    "ip_family"
400
    ]
401

    
402

    
403
class ConfigData(ConfigObject):
404
  """Top-level config object."""
405
  __slots__ = [
406
    "version",
407
    "cluster",
408
    "nodes",
409
    "nodegroups",
410
    "instances",
411
    "serial_no",
412
    ] + _TIMESTAMPS
413

    
414
  def ToDict(self):
415
    """Custom function for top-level config data.
416

417
    This just replaces the list of instances, nodes and the cluster
418
    with standard python types.
419

420
    """
421
    mydict = super(ConfigData, self).ToDict()
422
    mydict["cluster"] = mydict["cluster"].ToDict()
423
    for key in "nodes", "instances", "nodegroups":
424
      mydict[key] = self._ContainerToDicts(mydict[key])
425

    
426
    return mydict
427

    
428
  @classmethod
429
  def FromDict(cls, val):
430
    """Custom function for top-level config data
431

432
    """
433
    obj = super(ConfigData, cls).FromDict(val)
434
    obj.cluster = Cluster.FromDict(obj.cluster)
435
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
436
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
437
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
438
    return obj
439

    
440
  def HasAnyDiskOfType(self, dev_type):
441
    """Check if in there is at disk of the given type in the configuration.
442

443
    @type dev_type: L{constants.LDS_BLOCK}
444
    @param dev_type: the type to look for
445
    @rtype: boolean
446
    @return: boolean indicating if a disk of the given type was found or not
447

448
    """
449
    for instance in self.instances.values():
450
      for disk in instance.disks:
451
        if disk.IsBasedOnDiskType(dev_type):
452
          return True
453
    return False
454

    
455
  def UpgradeConfig(self):
456
    """Fill defaults for missing configuration values.
457

458
    """
459
    self.cluster.UpgradeConfig()
460
    for node in self.nodes.values():
461
      node.UpgradeConfig()
462
    for instance in self.instances.values():
463
      instance.UpgradeConfig()
464
    if self.nodegroups is None:
465
      self.nodegroups = {}
466
    for nodegroup in self.nodegroups.values():
467
      nodegroup.UpgradeConfig()
468
    if self.cluster.drbd_usermode_helper is None:
469
      # To decide if we set an helper let's check if at least one instance has
470
      # a DRBD disk. This does not cover all the possible scenarios but it
471
      # gives a good approximation.
472
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
473
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
474

    
475

    
476
class NIC(ConfigObject):
477
  """Config object representing a network card."""
478
  __slots__ = ["mac", "ip", "nicparams"]
479

    
480
  @classmethod
481
  def CheckParameterSyntax(cls, nicparams):
482
    """Check the given parameters for validity.
483

484
    @type nicparams:  dict
485
    @param nicparams: dictionary with parameter names/value
486
    @raise errors.ConfigurationError: when a parameter is not valid
487

488
    """
489
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
490
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
491
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
492
      raise errors.ConfigurationError(err)
493

    
494
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
495
        not nicparams[constants.NIC_LINK]):
496
      err = "Missing bridged nic link"
497
      raise errors.ConfigurationError(err)
498

    
499

    
500
class Disk(ConfigObject):
501
  """Config object representing a block device."""
502
  __slots__ = ["dev_type", "logical_id", "physical_id",
503
               "children", "iv_name", "size", "mode", "params"]
504

    
505
  def CreateOnSecondary(self):
506
    """Test if this device needs to be created on a secondary node."""
507
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
508

    
509
  def AssembleOnSecondary(self):
510
    """Test if this device needs to be assembled on a secondary node."""
511
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
512

    
513
  def OpenOnSecondary(self):
514
    """Test if this device needs to be opened on a secondary node."""
515
    return self.dev_type in (constants.LD_LV,)
516

    
517
  def StaticDevPath(self):
518
    """Return the device path if this device type has a static one.
519

520
    Some devices (LVM for example) live always at the same /dev/ path,
521
    irrespective of their status. For such devices, we return this
522
    path, for others we return None.
523

524
    @warning: The path returned is not a normalized pathname; callers
525
        should check that it is a valid path.
526

527
    """
528
    if self.dev_type == constants.LD_LV:
529
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
530
    elif self.dev_type == constants.LD_BLOCKDEV:
531
      return self.logical_id[1]
532
    return None
533

    
534
  def ChildrenNeeded(self):
535
    """Compute the needed number of children for activation.
536

537
    This method will return either -1 (all children) or a positive
538
    number denoting the minimum number of children needed for
539
    activation (only mirrored devices will usually return >=0).
540

541
    Currently, only DRBD8 supports diskless activation (therefore we
542
    return 0), for all other we keep the previous semantics and return
543
    -1.
544

545
    """
546
    if self.dev_type == constants.LD_DRBD8:
547
      return 0
548
    return -1
549

    
550
  def IsBasedOnDiskType(self, dev_type):
551
    """Check if the disk or its children are based on the given type.
552

553
    @type dev_type: L{constants.LDS_BLOCK}
554
    @param dev_type: the type to look for
555
    @rtype: boolean
556
    @return: boolean indicating if a device of the given type was found or not
557

558
    """
559
    if self.children:
560
      for child in self.children:
561
        if child.IsBasedOnDiskType(dev_type):
562
          return True
563
    return self.dev_type == dev_type
564

    
565
  def GetNodes(self, node):
566
    """This function returns the nodes this device lives on.
567

568
    Given the node on which the parent of the device lives on (or, in
569
    case of a top-level device, the primary node of the devices'
570
    instance), this function will return a list of nodes on which this
571
    devices needs to (or can) be assembled.
572

573
    """
574
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
575
                         constants.LD_BLOCKDEV]:
576
      result = [node]
577
    elif self.dev_type in constants.LDS_DRBD:
578
      result = [self.logical_id[0], self.logical_id[1]]
579
      if node not in result:
580
        raise errors.ConfigurationError("DRBD device passed unknown node")
581
    else:
582
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
583
    return result
584

    
585
  def ComputeNodeTree(self, parent_node):
586
    """Compute the node/disk tree for this disk and its children.
587

588
    This method, given the node on which the parent disk lives, will
589
    return the list of all (node, disk) pairs which describe the disk
590
    tree in the most compact way. For example, a drbd/lvm stack
591
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
592
    which represents all the top-level devices on the nodes.
593

594
    """
595
    my_nodes = self.GetNodes(parent_node)
596
    result = [(node, self) for node in my_nodes]
597
    if not self.children:
598
      # leaf device
599
      return result
600
    for node in my_nodes:
601
      for child in self.children:
602
        child_result = child.ComputeNodeTree(node)
603
        if len(child_result) == 1:
604
          # child (and all its descendants) is simple, doesn't split
605
          # over multiple hosts, so we don't need to describe it, our
606
          # own entry for this node describes it completely
607
          continue
608
        else:
609
          # check if child nodes differ from my nodes; note that
610
          # subdisk can differ from the child itself, and be instead
611
          # one of its descendants
612
          for subnode, subdisk in child_result:
613
            if subnode not in my_nodes:
614
              result.append((subnode, subdisk))
615
            # otherwise child is under our own node, so we ignore this
616
            # entry (but probably the other results in the list will
617
            # be different)
618
    return result
619

    
620
  def ComputeGrowth(self, amount):
621
    """Compute the per-VG growth requirements.
622

623
    This only works for VG-based disks.
624

625
    @type amount: integer
626
    @param amount: the desired increase in (user-visible) disk space
627
    @rtype: dict
628
    @return: a dictionary of volume-groups and the required size
629

630
    """
631
    if self.dev_type == constants.LD_LV:
632
      return {self.logical_id[0]: amount}
633
    elif self.dev_type == constants.LD_DRBD8:
634
      if self.children:
635
        return self.children[0].ComputeGrowth(amount)
636
      else:
637
        return {}
638
    else:
639
      # Other disk types do not require VG space
640
      return {}
641

    
642
  def RecordGrow(self, amount):
643
    """Update the size of this disk after growth.
644

645
    This method recurses over the disks's children and updates their
646
    size correspondigly. The method needs to be kept in sync with the
647
    actual algorithms from bdev.
648

649
    """
650
    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
651
      self.size += amount
652
    elif self.dev_type == constants.LD_DRBD8:
653
      if self.children:
654
        self.children[0].RecordGrow(amount)
655
      self.size += amount
656
    else:
657
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
658
                                   " disk type %s" % self.dev_type)
659

    
660
  def UnsetSize(self):
661
    """Sets recursively the size to zero for the disk and its children.
662

663
    """
664
    if self.children:
665
      for child in self.children:
666
        child.UnsetSize()
667
    self.size = 0
668

    
669
  def SetPhysicalID(self, target_node, nodes_ip):
670
    """Convert the logical ID to the physical ID.
671

672
    This is used only for drbd, which needs ip/port configuration.
673

674
    The routine descends down and updates its children also, because
675
    this helps when the only the top device is passed to the remote
676
    node.
677

678
    Arguments:
679
      - target_node: the node we wish to configure for
680
      - nodes_ip: a mapping of node name to ip
681

682
    The target_node must exist in in nodes_ip, and must be one of the
683
    nodes in the logical ID for each of the DRBD devices encountered
684
    in the disk tree.
685

686
    """
687
    if self.children:
688
      for child in self.children:
689
        child.SetPhysicalID(target_node, nodes_ip)
690

    
691
    if self.logical_id is None and self.physical_id is not None:
692
      return
693
    if self.dev_type in constants.LDS_DRBD:
694
      pnode, snode, port, pminor, sminor, secret = self.logical_id
695
      if target_node not in (pnode, snode):
696
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
697
                                        target_node)
698
      pnode_ip = nodes_ip.get(pnode, None)
699
      snode_ip = nodes_ip.get(snode, None)
700
      if pnode_ip is None or snode_ip is None:
701
        raise errors.ConfigurationError("Can't find primary or secondary node"
702
                                        " for %s" % str(self))
703
      p_data = (pnode_ip, port)
704
      s_data = (snode_ip, port)
705
      if pnode == target_node:
706
        self.physical_id = p_data + s_data + (pminor, secret)
707
      else: # it must be secondary, we tested above
708
        self.physical_id = s_data + p_data + (sminor, secret)
709
    else:
710
      self.physical_id = self.logical_id
711
    return
712

    
713
  def ToDict(self):
714
    """Disk-specific conversion to standard python types.
715

716
    This replaces the children lists of objects with lists of
717
    standard python types.
718

719
    """
720
    bo = super(Disk, self).ToDict()
721

    
722
    for attr in ("children",):
723
      alist = bo.get(attr, None)
724
      if alist:
725
        bo[attr] = self._ContainerToDicts(alist)
726
    return bo
727

    
728
  @classmethod
729
  def FromDict(cls, val):
730
    """Custom function for Disks
731

732
    """
733
    obj = super(Disk, cls).FromDict(val)
734
    if obj.children:
735
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
736
    if obj.logical_id and isinstance(obj.logical_id, list):
737
      obj.logical_id = tuple(obj.logical_id)
738
    if obj.physical_id and isinstance(obj.physical_id, list):
739
      obj.physical_id = tuple(obj.physical_id)
740
    if obj.dev_type in constants.LDS_DRBD:
741
      # we need a tuple of length six here
742
      if len(obj.logical_id) < 6:
743
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
744
    return obj
745

    
746
  def __str__(self):
747
    """Custom str() formatter for disks.
748

749
    """
750
    if self.dev_type == constants.LD_LV:
751
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
752
    elif self.dev_type in constants.LDS_DRBD:
753
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
754
      val = "<DRBD8("
755
      if self.physical_id is None:
756
        phy = "unconfigured"
757
      else:
758
        phy = ("configured as %s:%s %s:%s" %
759
               (self.physical_id[0], self.physical_id[1],
760
                self.physical_id[2], self.physical_id[3]))
761

    
762
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
763
              (node_a, minor_a, node_b, minor_b, port, phy))
764
      if self.children and self.children.count(None) == 0:
765
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
766
      else:
767
        val += "no local storage"
768
    else:
769
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
770
             (self.dev_type, self.logical_id, self.physical_id, self.children))
771
    if self.iv_name is None:
772
      val += ", not visible"
773
    else:
774
      val += ", visible as /dev/%s" % self.iv_name
775
    if isinstance(self.size, int):
776
      val += ", size=%dm)>" % self.size
777
    else:
778
      val += ", size='%s')>" % (self.size,)
779
    return val
780

    
781
  def Verify(self):
782
    """Checks that this disk is correctly configured.
783

784
    """
785
    all_errors = []
786
    if self.mode not in constants.DISK_ACCESS_SET:
787
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
788
    return all_errors
789

    
790
  def UpgradeConfig(self):
791
    """Fill defaults for missing configuration values.
792

793
    """
794
    if self.children:
795
      for child in self.children:
796
        child.UpgradeConfig()
797

    
798
    if not self.params:
799
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
800
    else:
801
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
802
                             self.params)
803
    # add here config upgrade for this disk
804

    
805

    
806
class InstancePolicy(ConfigObject):
807
  """Config object representing instance policy limits dictionary."""
808
  __slots__ = ["min", "max", "std"]
809

    
810
  @classmethod
811
  def CheckParameterSyntax(cls, ipolicy):
812
    """ Check the instance policy for validity.
813

814
    """
815
    for param in constants.ISPECS_PARAMETERS:
816
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
817

    
818
  @classmethod
819
  def CheckISpecSyntax(cls, ipolicy, name):
820
    """Check the instance policy for validity on a given key.
821

822
    We check if the instance policy makes sense for a given key, that is
823
    if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
824

825
    @type ipolicy: dict
826
    @param ipolicy: dictionary with min, max, std specs
827
    @type name: string
828
    @param name: what are the limits for
829
    @raise errors.ConfigureError: when specs for given name are not valid
830

831
    """
832
    min_v = ipolicy[constants.MIN_ISPECS].get(name, 0)
833
    std_v = ipolicy[constants.STD_ISPECS].get(name, min_v)
834
    max_v = ipolicy[constants.MAX_ISPECS].get(name, std_v)
835
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
836
           (name,
837
            ipolicy[constants.MIN_ISPECS].get(name, "-"),
838
            ipolicy[constants.MAX_ISPECS].get(name, "-"),
839
            ipolicy[constants.STD_ISPECS].get(name, "-")))
840
    if min_v > std_v or std_v > max_v:
841
      raise errors.ConfigurationError(err)
842

    
843

    
844
class Instance(TaggableObject):
845
  """Config object representing an instance."""
846
  __slots__ = [
847
    "name",
848
    "primary_node",
849
    "os",
850
    "hypervisor",
851
    "hvparams",
852
    "beparams",
853
    "osparams",
854
    "admin_state",
855
    "nics",
856
    "disks",
857
    "disk_template",
858
    "network_port",
859
    "serial_no",
860
    ] + _TIMESTAMPS + _UUID
861

    
862
  def _ComputeSecondaryNodes(self):
863
    """Compute the list of secondary nodes.
864

865
    This is a simple wrapper over _ComputeAllNodes.
866

867
    """
868
    all_nodes = set(self._ComputeAllNodes())
869
    all_nodes.discard(self.primary_node)
870
    return tuple(all_nodes)
871

    
872
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
873
                             "List of secondary nodes")
874

    
875
  def _ComputeAllNodes(self):
876
    """Compute the list of all nodes.
877

878
    Since the data is already there (in the drbd disks), keeping it as
879
    a separate normal attribute is redundant and if not properly
880
    synchronised can cause problems. Thus it's better to compute it
881
    dynamically.
882

883
    """
884
    def _Helper(nodes, device):
885
      """Recursively computes nodes given a top device."""
886
      if device.dev_type in constants.LDS_DRBD:
887
        nodea, nodeb = device.logical_id[:2]
888
        nodes.add(nodea)
889
        nodes.add(nodeb)
890
      if device.children:
891
        for child in device.children:
892
          _Helper(nodes, child)
893

    
894
    all_nodes = set()
895
    all_nodes.add(self.primary_node)
896
    for device in self.disks:
897
      _Helper(all_nodes, device)
898
    return tuple(all_nodes)
899

    
900
  all_nodes = property(_ComputeAllNodes, None, None,
901
                       "List of all nodes of the instance")
902

    
903
  def MapLVsByNode(self, lvmap=None, devs=None, node=None):
904
    """Provide a mapping of nodes to LVs this instance owns.
905

906
    This function figures out what logical volumes should belong on
907
    which nodes, recursing through a device tree.
908

909
    @param lvmap: optional dictionary to receive the
910
        'node' : ['lv', ...] data.
911

912
    @return: None if lvmap arg is given, otherwise, a dictionary of
913
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
914
        volumeN is of the form "vg_name/lv_name", compatible with
915
        GetVolumeList()
916

917
    """
918
    if node == None:
919
      node = self.primary_node
920

    
921
    if lvmap is None:
922
      lvmap = {
923
        node: [],
924
        }
925
      ret = lvmap
926
    else:
927
      if not node in lvmap:
928
        lvmap[node] = []
929
      ret = None
930

    
931
    if not devs:
932
      devs = self.disks
933

    
934
    for dev in devs:
935
      if dev.dev_type == constants.LD_LV:
936
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
937

    
938
      elif dev.dev_type in constants.LDS_DRBD:
939
        if dev.children:
940
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
941
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
942

    
943
      elif dev.children:
944
        self.MapLVsByNode(lvmap, dev.children, node)
945

    
946
    return ret
947

    
948
  def FindDisk(self, idx):
949
    """Find a disk given having a specified index.
950

951
    This is just a wrapper that does validation of the index.
952

953
    @type idx: int
954
    @param idx: the disk index
955
    @rtype: L{Disk}
956
    @return: the corresponding disk
957
    @raise errors.OpPrereqError: when the given index is not valid
958

959
    """
960
    try:
961
      idx = int(idx)
962
      return self.disks[idx]
963
    except (TypeError, ValueError), err:
964
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
965
                                 errors.ECODE_INVAL)
966
    except IndexError:
967
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
968
                                 " 0 to %d" % (idx, len(self.disks) - 1),
969
                                 errors.ECODE_INVAL)
970

    
971
  def ToDict(self):
972
    """Instance-specific conversion to standard python types.
973

974
    This replaces the children lists of objects with lists of standard
975
    python types.
976

977
    """
978
    bo = super(Instance, self).ToDict()
979

    
980
    for attr in "nics", "disks":
981
      alist = bo.get(attr, None)
982
      if alist:
983
        nlist = self._ContainerToDicts(alist)
984
      else:
985
        nlist = []
986
      bo[attr] = nlist
987
    return bo
988

    
989
  @classmethod
990
  def FromDict(cls, val):
991
    """Custom function for instances.
992

993
    """
994
    if "admin_state" not in val:
995
      if val.get("admin_up", False):
996
        val["admin_state"] = constants.ADMINST_UP
997
      else:
998
        val["admin_state"] = constants.ADMINST_DOWN
999
    if "admin_up" in val:
1000
      del val["admin_up"]
1001
    obj = super(Instance, cls).FromDict(val)
1002
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1003
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1004
    return obj
1005

    
1006
  def UpgradeConfig(self):
1007
    """Fill defaults for missing configuration values.
1008

1009
    """
1010
    for nic in self.nics:
1011
      nic.UpgradeConfig()
1012
    for disk in self.disks:
1013
      disk.UpgradeConfig()
1014
    if self.hvparams:
1015
      for key in constants.HVC_GLOBALS:
1016
        try:
1017
          del self.hvparams[key]
1018
        except KeyError:
1019
          pass
1020
    if self.osparams is None:
1021
      self.osparams = {}
1022
    UpgradeBeParams(self.beparams)
1023

    
1024

    
1025
class OS(ConfigObject):
1026
  """Config object representing an operating system.
1027

1028
  @type supported_parameters: list
1029
  @ivar supported_parameters: a list of tuples, name and description,
1030
      containing the supported parameters by this OS
1031

1032
  @type VARIANT_DELIM: string
1033
  @cvar VARIANT_DELIM: the variant delimiter
1034

1035
  """
1036
  __slots__ = [
1037
    "name",
1038
    "path",
1039
    "api_versions",
1040
    "create_script",
1041
    "export_script",
1042
    "import_script",
1043
    "rename_script",
1044
    "verify_script",
1045
    "supported_variants",
1046
    "supported_parameters",
1047
    ]
1048

    
1049
  VARIANT_DELIM = "+"
1050

    
1051
  @classmethod
1052
  def SplitNameVariant(cls, name):
1053
    """Splits the name into the proper name and variant.
1054

1055
    @param name: the OS (unprocessed) name
1056
    @rtype: list
1057
    @return: a list of two elements; if the original name didn't
1058
        contain a variant, it's returned as an empty string
1059

1060
    """
1061
    nv = name.split(cls.VARIANT_DELIM, 1)
1062
    if len(nv) == 1:
1063
      nv.append("")
1064
    return nv
1065

    
1066
  @classmethod
1067
  def GetName(cls, name):
1068
    """Returns the proper name of the os (without the variant).
1069

1070
    @param name: the OS (unprocessed) name
1071

1072
    """
1073
    return cls.SplitNameVariant(name)[0]
1074

    
1075
  @classmethod
1076
  def GetVariant(cls, name):
1077
    """Returns the variant the os (without the base name).
1078

1079
    @param name: the OS (unprocessed) name
1080

1081
    """
1082
    return cls.SplitNameVariant(name)[1]
1083

    
1084

    
1085
class NodeHvState(ConfigObject):
1086
  """Hypvervisor state on a node.
1087

1088
  @ivar mem_total: Total amount of memory
1089
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1090
    available)
1091
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1092
    rounding
1093
  @ivar mem_inst: Memory used by instances living on node
1094
  @ivar cpu_total: Total node CPU core count
1095
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1096

1097
  """
1098
  __slots__ = [
1099
    "mem_total",
1100
    "mem_node",
1101
    "mem_hv",
1102
    "mem_inst",
1103
    "cpu_total",
1104
    "cpu_node",
1105
    ] + _TIMESTAMPS
1106

    
1107

    
1108
class NodeDiskState(ConfigObject):
1109
  """Disk state on a node.
1110

1111
  """
1112
  __slots__ = [
1113
    "total",
1114
    "reserved",
1115
    "overhead",
1116
    ] + _TIMESTAMPS
1117

    
1118

    
1119
class Node(TaggableObject):
1120
  """Config object representing a node.
1121

1122
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1123
  @ivar hv_state_static: Hypervisor state overriden by user
1124
  @ivar disk_state: Disk state (e.g. free space)
1125
  @ivar disk_state_static: Disk state overriden by user
1126

1127
  """
1128
  __slots__ = [
1129
    "name",
1130
    "primary_ip",
1131
    "secondary_ip",
1132
    "serial_no",
1133
    "master_candidate",
1134
    "offline",
1135
    "drained",
1136
    "group",
1137
    "master_capable",
1138
    "vm_capable",
1139
    "ndparams",
1140
    "powered",
1141
    "hv_state",
1142
    "hv_state_static",
1143
    "disk_state",
1144
    "disk_state_static",
1145
    ] + _TIMESTAMPS + _UUID
1146

    
1147
  def UpgradeConfig(self):
1148
    """Fill defaults for missing configuration values.
1149

1150
    """
1151
    # pylint: disable=E0203
1152
    # because these are "defined" via slots, not manually
1153
    if self.master_capable is None:
1154
      self.master_capable = True
1155

    
1156
    if self.vm_capable is None:
1157
      self.vm_capable = True
1158

    
1159
    if self.ndparams is None:
1160
      self.ndparams = {}
1161

    
1162
    if self.powered is None:
1163
      self.powered = True
1164

    
1165
  def ToDict(self):
1166
    """Custom function for serializing.
1167

1168
    """
1169
    data = super(Node, self).ToDict()
1170

    
1171
    hv_state = data.get("hv_state", None)
1172
    if hv_state is not None:
1173
      data["hv_state"] = self._ContainerToDicts(hv_state)
1174

    
1175
    disk_state = data.get("disk_state", None)
1176
    if disk_state is not None:
1177
      data["disk_state"] = \
1178
        dict((key, self._ContainerToDicts(value))
1179
             for (key, value) in disk_state.items())
1180

    
1181
    return data
1182

    
1183
  @classmethod
1184
  def FromDict(cls, val):
1185
    """Custom function for deserializing.
1186

1187
    """
1188
    obj = super(Node, cls).FromDict(val)
1189

    
1190
    if obj.hv_state is not None:
1191
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1192

    
1193
    if obj.disk_state is not None:
1194
      obj.disk_state = \
1195
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1196
             for (key, value) in obj.disk_state.items())
1197

    
1198
    return obj
1199

    
1200

    
1201
class NodeGroup(TaggableObject):
1202
  """Config object representing a node group."""
1203
  __slots__ = [
1204
    "name",
1205
    "members",
1206
    "ndparams",
1207
    "diskparams",
1208
    "serial_no",
1209
    "hv_state_static",
1210
    "disk_state_static",
1211
    "alloc_policy",
1212
    ] + _TIMESTAMPS + _UUID
1213

    
1214
  def ToDict(self):
1215
    """Custom function for nodegroup.
1216

1217
    This discards the members object, which gets recalculated and is only kept
1218
    in memory.
1219

1220
    """
1221
    mydict = super(NodeGroup, self).ToDict()
1222
    del mydict["members"]
1223
    return mydict
1224

    
1225
  @classmethod
1226
  def FromDict(cls, val):
1227
    """Custom function for nodegroup.
1228

1229
    The members slot is initialized to an empty list, upon deserialization.
1230

1231
    """
1232
    obj = super(NodeGroup, cls).FromDict(val)
1233
    obj.members = []
1234
    return obj
1235

    
1236
  def UpgradeConfig(self):
1237
    """Fill defaults for missing configuration values.
1238

1239
    """
1240
    if self.ndparams is None:
1241
      self.ndparams = {}
1242

    
1243
    if self.serial_no is None:
1244
      self.serial_no = 1
1245

    
1246
    if self.alloc_policy is None:
1247
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1248

    
1249
    # We only update mtime, and not ctime, since we would not be able to provide
1250
    # a correct value for creation time.
1251
    if self.mtime is None:
1252
      self.mtime = time.time()
1253

    
1254
    self.diskparams = UpgradeDiskParams(self.diskparams)
1255

    
1256
  def FillND(self, node):
1257
    """Return filled out ndparams for L{objects.Node}
1258

1259
    @type node: L{objects.Node}
1260
    @param node: A Node object to fill
1261
    @return a copy of the node's ndparams with defaults filled
1262

1263
    """
1264
    return self.SimpleFillND(node.ndparams)
1265

    
1266
  def SimpleFillND(self, ndparams):
1267
    """Fill a given ndparams dict with defaults.
1268

1269
    @type ndparams: dict
1270
    @param ndparams: the dict to fill
1271
    @rtype: dict
1272
    @return: a copy of the passed in ndparams with missing keys filled
1273
        from the node group defaults
1274

1275
    """
1276
    return FillDict(self.ndparams, ndparams)
1277

    
1278

    
1279
class Cluster(TaggableObject):
1280
  """Config object representing the cluster."""
1281
  __slots__ = [
1282
    "serial_no",
1283
    "rsahostkeypub",
1284
    "highest_used_port",
1285
    "tcpudp_port_pool",
1286
    "mac_prefix",
1287
    "volume_group_name",
1288
    "reserved_lvs",
1289
    "drbd_usermode_helper",
1290
    "default_bridge",
1291
    "default_hypervisor",
1292
    "master_node",
1293
    "master_ip",
1294
    "master_netdev",
1295
    "master_netmask",
1296
    "use_external_mip_script",
1297
    "cluster_name",
1298
    "file_storage_dir",
1299
    "shared_file_storage_dir",
1300
    "enabled_hypervisors",
1301
    "hvparams",
1302
    "ipolicy",
1303
    "os_hvp",
1304
    "beparams",
1305
    "osparams",
1306
    "nicparams",
1307
    "ndparams",
1308
    "diskparams",
1309
    "candidate_pool_size",
1310
    "modify_etc_hosts",
1311
    "modify_ssh_setup",
1312
    "maintain_node_health",
1313
    "uid_pool",
1314
    "default_iallocator",
1315
    "hidden_os",
1316
    "blacklisted_os",
1317
    "primary_ip_family",
1318
    "prealloc_wipe_disks",
1319
    "hv_state_static",
1320
    "disk_state_static",
1321
    ] + _TIMESTAMPS + _UUID
1322

    
1323
  def UpgradeConfig(self):
1324
    """Fill defaults for missing configuration values.
1325

1326
    """
1327
    # pylint: disable=E0203
1328
    # because these are "defined" via slots, not manually
1329
    if self.hvparams is None:
1330
      self.hvparams = constants.HVC_DEFAULTS
1331
    else:
1332
      for hypervisor in self.hvparams:
1333
        self.hvparams[hypervisor] = FillDict(
1334
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1335

    
1336
    if self.os_hvp is None:
1337
      self.os_hvp = {}
1338

    
1339
    # osparams added before 2.2
1340
    if self.osparams is None:
1341
      self.osparams = {}
1342

    
1343
    if self.ndparams is None:
1344
      self.ndparams = constants.NDC_DEFAULTS
1345

    
1346
    self.beparams = UpgradeGroupedParams(self.beparams,
1347
                                         constants.BEC_DEFAULTS)
1348
    for beparams_group in self.beparams:
1349
      UpgradeBeParams(self.beparams[beparams_group])
1350

    
1351
    migrate_default_bridge = not self.nicparams
1352
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1353
                                          constants.NICC_DEFAULTS)
1354
    if migrate_default_bridge:
1355
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1356
        self.default_bridge
1357

    
1358
    if self.modify_etc_hosts is None:
1359
      self.modify_etc_hosts = True
1360

    
1361
    if self.modify_ssh_setup is None:
1362
      self.modify_ssh_setup = True
1363

    
1364
    # default_bridge is no longer used in 2.1. The slot is left there to
1365
    # support auto-upgrading. It can be removed once we decide to deprecate
1366
    # upgrading straight from 2.0.
1367
    if self.default_bridge is not None:
1368
      self.default_bridge = None
1369

    
1370
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1371
    # code can be removed once upgrading straight from 2.0 is deprecated.
1372
    if self.default_hypervisor is not None:
1373
      self.enabled_hypervisors = ([self.default_hypervisor] +
1374
        [hvname for hvname in self.enabled_hypervisors
1375
         if hvname != self.default_hypervisor])
1376
      self.default_hypervisor = None
1377

    
1378
    # maintain_node_health added after 2.1.1
1379
    if self.maintain_node_health is None:
1380
      self.maintain_node_health = False
1381

    
1382
    if self.uid_pool is None:
1383
      self.uid_pool = []
1384

    
1385
    if self.default_iallocator is None:
1386
      self.default_iallocator = ""
1387

    
1388
    # reserved_lvs added before 2.2
1389
    if self.reserved_lvs is None:
1390
      self.reserved_lvs = []
1391

    
1392
    # hidden and blacklisted operating systems added before 2.2.1
1393
    if self.hidden_os is None:
1394
      self.hidden_os = []
1395

    
1396
    if self.blacklisted_os is None:
1397
      self.blacklisted_os = []
1398

    
1399
    # primary_ip_family added before 2.3
1400
    if self.primary_ip_family is None:
1401
      self.primary_ip_family = AF_INET
1402

    
1403
    if self.master_netmask is None:
1404
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1405
      self.master_netmask = ipcls.iplen
1406

    
1407
    if self.prealloc_wipe_disks is None:
1408
      self.prealloc_wipe_disks = False
1409

    
1410
    # shared_file_storage_dir added before 2.5
1411
    if self.shared_file_storage_dir is None:
1412
      self.shared_file_storage_dir = ""
1413

    
1414
    if self.use_external_mip_script is None:
1415
      self.use_external_mip_script = False
1416

    
1417
    self.diskparams = UpgradeDiskParams(self.diskparams)
1418

    
1419
    # instance policy added before 2.6
1420
    if self.ipolicy is None:
1421
      self.ipolicy = MakeEmptyIPolicy()
1422

    
1423
  @property
1424
  def primary_hypervisor(self):
1425
    """The first hypervisor is the primary.
1426

1427
    Useful, for example, for L{Node}'s hv/disk state.
1428

1429
    """
1430
    return self.enabled_hypervisors[0]
1431

    
1432
  def ToDict(self):
1433
    """Custom function for cluster.
1434

1435
    """
1436
    mydict = super(Cluster, self).ToDict()
1437
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1438
    return mydict
1439

    
1440
  @classmethod
1441
  def FromDict(cls, val):
1442
    """Custom function for cluster.
1443

1444
    """
1445
    obj = super(Cluster, cls).FromDict(val)
1446
    if not isinstance(obj.tcpudp_port_pool, set):
1447
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1448
    return obj
1449

    
1450
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1451
    """Get the default hypervisor parameters for the cluster.
1452

1453
    @param hypervisor: the hypervisor name
1454
    @param os_name: if specified, we'll also update the defaults for this OS
1455
    @param skip_keys: if passed, list of keys not to use
1456
    @return: the defaults dict
1457

1458
    """
1459
    if skip_keys is None:
1460
      skip_keys = []
1461

    
1462
    fill_stack = [self.hvparams.get(hypervisor, {})]
1463
    if os_name is not None:
1464
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1465
      fill_stack.append(os_hvp)
1466

    
1467
    ret_dict = {}
1468
    for o_dict in fill_stack:
1469
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1470

    
1471
    return ret_dict
1472

    
1473
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1474
    """Fill a given hvparams dict with cluster defaults.
1475

1476
    @type hv_name: string
1477
    @param hv_name: the hypervisor to use
1478
    @type os_name: string
1479
    @param os_name: the OS to use for overriding the hypervisor defaults
1480
    @type skip_globals: boolean
1481
    @param skip_globals: if True, the global hypervisor parameters will
1482
        not be filled
1483
    @rtype: dict
1484
    @return: a copy of the given hvparams with missing keys filled from
1485
        the cluster defaults
1486

1487
    """
1488
    if skip_globals:
1489
      skip_keys = constants.HVC_GLOBALS
1490
    else:
1491
      skip_keys = []
1492

    
1493
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1494
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1495

    
1496
  def FillHV(self, instance, skip_globals=False):
1497
    """Fill an instance's hvparams dict with cluster defaults.
1498

1499
    @type instance: L{objects.Instance}
1500
    @param instance: the instance parameter to fill
1501
    @type skip_globals: boolean
1502
    @param skip_globals: if True, the global hypervisor parameters will
1503
        not be filled
1504
    @rtype: dict
1505
    @return: a copy of the instance's hvparams with missing keys filled from
1506
        the cluster defaults
1507

1508
    """
1509
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1510
                             instance.hvparams, skip_globals)
1511

    
1512
  def SimpleFillBE(self, beparams):
1513
    """Fill a given beparams dict with cluster defaults.
1514

1515
    @type beparams: dict
1516
    @param beparams: the dict to fill
1517
    @rtype: dict
1518
    @return: a copy of the passed in beparams with missing keys filled
1519
        from the cluster defaults
1520

1521
    """
1522
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1523

    
1524
  def FillBE(self, instance):
1525
    """Fill an instance's beparams dict with cluster defaults.
1526

1527
    @type instance: L{objects.Instance}
1528
    @param instance: the instance parameter to fill
1529
    @rtype: dict
1530
    @return: a copy of the instance's beparams with missing keys filled from
1531
        the cluster defaults
1532

1533
    """
1534
    return self.SimpleFillBE(instance.beparams)
1535

    
1536
  def SimpleFillNIC(self, nicparams):
1537
    """Fill a given nicparams dict with cluster defaults.
1538

1539
    @type nicparams: dict
1540
    @param nicparams: the dict to fill
1541
    @rtype: dict
1542
    @return: a copy of the passed in nicparams with missing keys filled
1543
        from the cluster defaults
1544

1545
    """
1546
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1547

    
1548
  def SimpleFillOS(self, os_name, os_params):
1549
    """Fill an instance's osparams dict with cluster defaults.
1550

1551
    @type os_name: string
1552
    @param os_name: the OS name to use
1553
    @type os_params: dict
1554
    @param os_params: the dict to fill with default values
1555
    @rtype: dict
1556
    @return: a copy of the instance's osparams with missing keys filled from
1557
        the cluster defaults
1558

1559
    """
1560
    name_only = os_name.split("+", 1)[0]
1561
    # base OS
1562
    result = self.osparams.get(name_only, {})
1563
    # OS with variant
1564
    result = FillDict(result, self.osparams.get(os_name, {}))
1565
    # specified params
1566
    return FillDict(result, os_params)
1567

    
1568
  @staticmethod
1569
  def SimpleFillHvState(hv_state):
1570
    """Fill an hv_state sub dict with cluster defaults.
1571

1572
    """
1573
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1574

    
1575
  @staticmethod
1576
  def SimpleFillDiskState(disk_state):
1577
    """Fill an disk_state sub dict with cluster defaults.
1578

1579
    """
1580
    return FillDict(constants.DS_DEFAULTS, disk_state)
1581

    
1582
  def FillND(self, node, nodegroup):
1583
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1584

1585
    @type node: L{objects.Node}
1586
    @param node: A Node object to fill
1587
    @type nodegroup: L{objects.NodeGroup}
1588
    @param nodegroup: A Node object to fill
1589
    @return a copy of the node's ndparams with defaults filled
1590

1591
    """
1592
    return self.SimpleFillND(nodegroup.FillND(node))
1593

    
1594
  def SimpleFillND(self, ndparams):
1595
    """Fill a given ndparams dict with defaults.
1596

1597
    @type ndparams: dict
1598
    @param ndparams: the dict to fill
1599
    @rtype: dict
1600
    @return: a copy of the passed in ndparams with missing keys filled
1601
        from the cluster defaults
1602

1603
    """
1604
    return FillDict(self.ndparams, ndparams)
1605

    
1606
  def SimpleFillIPolicy(self, ipolicy):
1607
    """ Fill instance policy dict with defaults.
1608

1609
    @type ipolicy: dict
1610
    @param ipolicy: the dict to fill
1611
    @rtype: dict
1612
    @return: a copy of passed ipolicy with missing keys filled from
1613
      the cluster defaults
1614

1615
    """
1616
    return FillDictOfDicts(self.ipolicy, ipolicy)
1617

    
1618

    
1619
class BlockDevStatus(ConfigObject):
1620
  """Config object representing the status of a block device."""
1621
  __slots__ = [
1622
    "dev_path",
1623
    "major",
1624
    "minor",
1625
    "sync_percent",
1626
    "estimated_time",
1627
    "is_degraded",
1628
    "ldisk_status",
1629
    ]
1630

    
1631

    
1632
class ImportExportStatus(ConfigObject):
1633
  """Config object representing the status of an import or export."""
1634
  __slots__ = [
1635
    "recent_output",
1636
    "listen_port",
1637
    "connected",
1638
    "progress_mbytes",
1639
    "progress_throughput",
1640
    "progress_eta",
1641
    "progress_percent",
1642
    "exit_status",
1643
    "error_message",
1644
    ] + _TIMESTAMPS
1645

    
1646

    
1647
class ImportExportOptions(ConfigObject):
1648
  """Options for import/export daemon
1649

1650
  @ivar key_name: X509 key name (None for cluster certificate)
1651
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1652
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1653
  @ivar magic: Used to ensure the connection goes to the right disk
1654
  @ivar ipv6: Whether to use IPv6
1655
  @ivar connect_timeout: Number of seconds for establishing connection
1656

1657
  """
1658
  __slots__ = [
1659
    "key_name",
1660
    "ca_pem",
1661
    "compress",
1662
    "magic",
1663
    "ipv6",
1664
    "connect_timeout",
1665
    ]
1666

    
1667

    
1668
class ConfdRequest(ConfigObject):
1669
  """Object holding a confd request.
1670

1671
  @ivar protocol: confd protocol version
1672
  @ivar type: confd query type
1673
  @ivar query: query request
1674
  @ivar rsalt: requested reply salt
1675

1676
  """
1677
  __slots__ = [
1678
    "protocol",
1679
    "type",
1680
    "query",
1681
    "rsalt",
1682
    ]
1683

    
1684

    
1685
class ConfdReply(ConfigObject):
1686
  """Object holding a confd reply.
1687

1688
  @ivar protocol: confd protocol version
1689
  @ivar status: reply status code (ok, error)
1690
  @ivar answer: confd query reply
1691
  @ivar serial: configuration serial number
1692

1693
  """
1694
  __slots__ = [
1695
    "protocol",
1696
    "status",
1697
    "answer",
1698
    "serial",
1699
    ]
1700

    
1701

    
1702
class QueryFieldDefinition(ConfigObject):
1703
  """Object holding a query field definition.
1704

1705
  @ivar name: Field name
1706
  @ivar title: Human-readable title
1707
  @ivar kind: Field type
1708
  @ivar doc: Human-readable description
1709

1710
  """
1711
  __slots__ = [
1712
    "name",
1713
    "title",
1714
    "kind",
1715
    "doc",
1716
    ]
1717

    
1718

    
1719
class _QueryResponseBase(ConfigObject):
1720
  __slots__ = [
1721
    "fields",
1722
    ]
1723

    
1724
  def ToDict(self):
1725
    """Custom function for serializing.
1726

1727
    """
1728
    mydict = super(_QueryResponseBase, self).ToDict()
1729
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1730
    return mydict
1731

    
1732
  @classmethod
1733
  def FromDict(cls, val):
1734
    """Custom function for de-serializing.
1735

1736
    """
1737
    obj = super(_QueryResponseBase, cls).FromDict(val)
1738
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1739
    return obj
1740

    
1741

    
1742
class QueryRequest(ConfigObject):
1743
  """Object holding a query request.
1744

1745
  """
1746
  __slots__ = [
1747
    "what",
1748
    "fields",
1749
    "qfilter",
1750
    ]
1751

    
1752

    
1753
class QueryResponse(_QueryResponseBase):
1754
  """Object holding the response to a query.
1755

1756
  @ivar fields: List of L{QueryFieldDefinition} objects
1757
  @ivar data: Requested data
1758

1759
  """
1760
  __slots__ = [
1761
    "data",
1762
    ]
1763

    
1764

    
1765
class QueryFieldsRequest(ConfigObject):
1766
  """Object holding a request for querying available fields.
1767

1768
  """
1769
  __slots__ = [
1770
    "what",
1771
    "fields",
1772
    ]
1773

    
1774

    
1775
class QueryFieldsResponse(_QueryResponseBase):
1776
  """Object holding the response to a query for fields.
1777

1778
  @ivar fields: List of L{QueryFieldDefinition} objects
1779

1780
  """
1781
  __slots__ = [
1782
    ]
1783

    
1784

    
1785
class MigrationStatus(ConfigObject):
1786
  """Object holding the status of a migration.
1787

1788
  """
1789
  __slots__ = [
1790
    "status",
1791
    "transferred_ram",
1792
    "total_ram",
1793
    ]
1794

    
1795

    
1796
class InstanceConsole(ConfigObject):
1797
  """Object describing how to access the console of an instance.
1798

1799
  """
1800
  __slots__ = [
1801
    "instance",
1802
    "kind",
1803
    "message",
1804
    "host",
1805
    "port",
1806
    "user",
1807
    "command",
1808
    "display",
1809
    ]
1810

    
1811
  def Validate(self):
1812
    """Validates contents of this object.
1813

1814
    """
1815
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1816
    assert self.instance, "Missing instance name"
1817
    assert self.message or self.kind in [constants.CONS_SSH,
1818
                                         constants.CONS_SPICE,
1819
                                         constants.CONS_VNC]
1820
    assert self.host or self.kind == constants.CONS_MESSAGE
1821
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1822
                                      constants.CONS_SSH]
1823
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1824
                                      constants.CONS_SPICE,
1825
                                      constants.CONS_VNC]
1826
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1827
                                         constants.CONS_SPICE,
1828
                                         constants.CONS_VNC]
1829
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1830
                                         constants.CONS_SPICE,
1831
                                         constants.CONS_SSH]
1832
    return True
1833

    
1834

    
1835
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1836
  """Simple wrapper over ConfigParse that allows serialization.
1837

1838
  This class is basically ConfigParser.SafeConfigParser with two
1839
  additional methods that allow it to serialize/unserialize to/from a
1840
  buffer.
1841

1842
  """
1843
  def Dumps(self):
1844
    """Dump this instance and return the string representation."""
1845
    buf = StringIO()
1846
    self.write(buf)
1847
    return buf.getvalue()
1848

    
1849
  @classmethod
1850
  def Loads(cls, data):
1851
    """Load data from a string."""
1852
    buf = StringIO(data)
1853
    cfp = cls()
1854
    cfp.readfp(buf)
1855
    return cfp