Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 33be7576

History | View | Annotate | Download (44.9 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 UpgradeGroupedParams(target, defaults):
83
  """Update all groups for the target parameter.
84

85
  @type target: dict of dicts
86
  @param target: {group: {parameter: value}}
87
  @type defaults: dict
88
  @param defaults: default parameter values
89

90
  """
91
  if target is None:
92
    target = {constants.PP_DEFAULT: defaults}
93
  else:
94
    for group in target:
95
      target[group] = FillDict(defaults, target[group])
96
  return target
97

    
98

    
99
class ConfigObject(object):
100
  """A generic config object.
101

102
  It has the following properties:
103

104
    - provides somewhat safe recursive unpickling and pickling for its classes
105
    - unset attributes which are defined in slots are always returned
106
      as None instead of raising an error
107

108
  Classes derived from this must always declare __slots__ (we use many
109
  config objects and the memory reduction is useful)
110

111
  """
112
  __slots__ = []
113

    
114
  def __init__(self, **kwargs):
115
    for k, v in kwargs.iteritems():
116
      setattr(self, k, v)
117

    
118
  def __getattr__(self, name):
119
    if name not in self._all_slots():
120
      raise AttributeError("Invalid object attribute %s.%s" %
121
                           (type(self).__name__, name))
122
    return None
123

    
124
  def __setstate__(self, state):
125
    slots = self._all_slots()
126
    for name in state:
127
      if name in slots:
128
        setattr(self, name, state[name])
129

    
130
  @classmethod
131
  def _all_slots(cls):
132
    """Compute the list of all declared slots for a class.
133

134
    """
135
    slots = []
136
    for parent in cls.__mro__:
137
      slots.extend(getattr(parent, "__slots__", []))
138
    return slots
139

    
140
  def ToDict(self):
141
    """Convert to a dict holding only standard python types.
142

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

149
    """
150
    result = {}
151
    for name in self._all_slots():
152
      value = getattr(self, name, None)
153
      if value is not None:
154
        result[name] = value
155
    return result
156

    
157
  __getstate__ = ToDict
158

    
159
  @classmethod
160
  def FromDict(cls, val):
161
    """Create an object from a dictionary.
162

163
    This generic routine takes a dict, instantiates a new instance of
164
    the given class, and sets attributes based on the dict content.
165

166
    As for `ToDict`, this does not work if the class has children
167
    who are ConfigObjects themselves (e.g. the nics list in an
168
    Instance), in which case the object should subclass the function
169
    and alter the objects.
170

171
    """
172
    if not isinstance(val, dict):
173
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
174
                                      " expected dict, got %s" % type(val))
175
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
176
    obj = cls(**val_str) # pylint: disable=W0142
177
    return obj
178

    
179
  @staticmethod
180
  def _ContainerToDicts(container):
181
    """Convert the elements of a container to standard python types.
182

183
    This method converts a container with elements derived from
184
    ConfigData to standard python types. If the container is a dict,
185
    we don't touch the keys, only the values.
186

187
    """
188
    if isinstance(container, dict):
189
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
190
    elif isinstance(container, (list, tuple, set, frozenset)):
191
      ret = [elem.ToDict() for elem in container]
192
    else:
193
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
194
                      type(container))
195
    return ret
196

    
197
  @staticmethod
198
  def _ContainerFromDicts(source, c_type, e_type):
199
    """Convert a container from standard python types.
200

201
    This method converts a container with standard python types to
202
    ConfigData objects. If the container is a dict, we don't touch the
203
    keys, only the values.
204

205
    """
206
    if not isinstance(c_type, type):
207
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
208
                      " not a type" % type(c_type))
209
    if source is None:
210
      source = c_type()
211
    if c_type is dict:
212
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
213
    elif c_type in (list, tuple, set, frozenset):
214
      ret = c_type([e_type.FromDict(elem) for elem in source])
215
    else:
216
      raise TypeError("Invalid container type %s passed to"
217
                      " _ContainerFromDicts" % c_type)
218
    return ret
219

    
220
  def Copy(self):
221
    """Makes a deep copy of the current object and its children.
222

223
    """
224
    dict_form = self.ToDict()
225
    clone_obj = self.__class__.FromDict(dict_form)
226
    return clone_obj
227

    
228
  def __repr__(self):
229
    """Implement __repr__ for ConfigObjects."""
230
    return repr(self.ToDict())
231

    
232
  def UpgradeConfig(self):
233
    """Fill defaults for missing configuration values.
234

235
    This method will be called at configuration load time, and its
236
    implementation will be object dependent.
237

238
    """
239
    pass
240

    
241

    
242
class TaggableObject(ConfigObject):
243
  """An generic class supporting tags.
244

245
  """
246
  __slots__ = ["tags"]
247
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
248

    
249
  @classmethod
250
  def ValidateTag(cls, tag):
251
    """Check if a tag is valid.
252

253
    If the tag is invalid, an errors.TagError will be raised. The
254
    function has no return value.
255

256
    """
257
    if not isinstance(tag, basestring):
258
      raise errors.TagError("Invalid tag type (not a string)")
259
    if len(tag) > constants.MAX_TAG_LEN:
260
      raise errors.TagError("Tag too long (>%d characters)" %
261
                            constants.MAX_TAG_LEN)
262
    if not tag:
263
      raise errors.TagError("Tags cannot be empty")
264
    if not cls.VALID_TAG_RE.match(tag):
265
      raise errors.TagError("Tag contains invalid characters")
266

    
267
  def GetTags(self):
268
    """Return the tags list.
269

270
    """
271
    tags = getattr(self, "tags", None)
272
    if tags is None:
273
      tags = self.tags = set()
274
    return tags
275

    
276
  def AddTag(self, tag):
277
    """Add a new tag.
278

279
    """
280
    self.ValidateTag(tag)
281
    tags = self.GetTags()
282
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
283
      raise errors.TagError("Too many tags")
284
    self.GetTags().add(tag)
285

    
286
  def RemoveTag(self, tag):
287
    """Remove a tag.
288

289
    """
290
    self.ValidateTag(tag)
291
    tags = self.GetTags()
292
    try:
293
      tags.remove(tag)
294
    except KeyError:
295
      raise errors.TagError("Tag not found")
296

    
297
  def ToDict(self):
298
    """Taggable-object-specific conversion to standard python types.
299

300
    This replaces the tags set with a list.
301

302
    """
303
    bo = super(TaggableObject, self).ToDict()
304

    
305
    tags = bo.get("tags", None)
306
    if isinstance(tags, set):
307
      bo["tags"] = list(tags)
308
    return bo
309

    
310
  @classmethod
311
  def FromDict(cls, val):
312
    """Custom function for instances.
313

314
    """
315
    obj = super(TaggableObject, cls).FromDict(val)
316
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
317
      obj.tags = set(obj.tags)
318
    return obj
319

    
320

    
321
class MasterNetworkParameters(ConfigObject):
322
  """Network configuration parameters for the master
323

324
  @ivar name: master name
325
  @ivar ip: master IP
326
  @ivar netmask: master netmask
327
  @ivar netdev: master network device
328
  @ivar ip_family: master IP family
329

330
  """
331
  __slots__ = [
332
    "name",
333
    "ip",
334
    "netmask",
335
    "netdev",
336
    "ip_family"
337
    ]
338

    
339

    
340
class ConfigData(ConfigObject):
341
  """Top-level config object."""
342
  __slots__ = [
343
    "version",
344
    "cluster",
345
    "nodes",
346
    "nodegroups",
347
    "instances",
348
    "serial_no",
349
    ] + _TIMESTAMPS
350

    
351
  def ToDict(self):
352
    """Custom function for top-level config data.
353

354
    This just replaces the list of instances, nodes and the cluster
355
    with standard python types.
356

357
    """
358
    mydict = super(ConfigData, self).ToDict()
359
    mydict["cluster"] = mydict["cluster"].ToDict()
360
    for key in "nodes", "instances", "nodegroups":
361
      mydict[key] = self._ContainerToDicts(mydict[key])
362

    
363
    return mydict
364

    
365
  @classmethod
366
  def FromDict(cls, val):
367
    """Custom function for top-level config data
368

369
    """
370
    obj = super(ConfigData, cls).FromDict(val)
371
    obj.cluster = Cluster.FromDict(obj.cluster)
372
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
373
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
374
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
375
    return obj
376

    
377
  def HasAnyDiskOfType(self, dev_type):
378
    """Check if in there is at disk of the given type in the configuration.
379

380
    @type dev_type: L{constants.LDS_BLOCK}
381
    @param dev_type: the type to look for
382
    @rtype: boolean
383
    @return: boolean indicating if a disk of the given type was found or not
384

385
    """
386
    for instance in self.instances.values():
387
      for disk in instance.disks:
388
        if disk.IsBasedOnDiskType(dev_type):
389
          return True
390
    return False
391

    
392
  def UpgradeConfig(self):
393
    """Fill defaults for missing configuration values.
394

395
    """
396
    self.cluster.UpgradeConfig()
397
    for node in self.nodes.values():
398
      node.UpgradeConfig()
399
    for instance in self.instances.values():
400
      instance.UpgradeConfig()
401
    if self.nodegroups is None:
402
      self.nodegroups = {}
403
    for nodegroup in self.nodegroups.values():
404
      nodegroup.UpgradeConfig()
405
    if self.cluster.drbd_usermode_helper is None:
406
      # To decide if we set an helper let's check if at least one instance has
407
      # a DRBD disk. This does not cover all the possible scenarios but it
408
      # gives a good approximation.
409
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
410
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
411

    
412

    
413
class NIC(ConfigObject):
414
  """Config object representing a network card."""
415
  __slots__ = ["mac", "ip", "nicparams"]
416

    
417
  @classmethod
418
  def CheckParameterSyntax(cls, nicparams):
419
    """Check the given parameters for validity.
420

421
    @type nicparams:  dict
422
    @param nicparams: dictionary with parameter names/value
423
    @raise errors.ConfigurationError: when a parameter is not valid
424

425
    """
426
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
427
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
428
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
429
      raise errors.ConfigurationError(err)
430

    
431
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
432
        not nicparams[constants.NIC_LINK]):
433
      err = "Missing bridged nic link"
434
      raise errors.ConfigurationError(err)
435

    
436

    
437
class Disk(ConfigObject):
438
  """Config object representing a block device."""
439
  __slots__ = ["dev_type", "logical_id", "physical_id",
440
               "children", "iv_name", "size", "mode"]
441

    
442
  def CreateOnSecondary(self):
443
    """Test if this device needs to be created on a secondary node."""
444
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
445

    
446
  def AssembleOnSecondary(self):
447
    """Test if this device needs to be assembled on a secondary node."""
448
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
449

    
450
  def OpenOnSecondary(self):
451
    """Test if this device needs to be opened on a secondary node."""
452
    return self.dev_type in (constants.LD_LV,)
453

    
454
  def StaticDevPath(self):
455
    """Return the device path if this device type has a static one.
456

457
    Some devices (LVM for example) live always at the same /dev/ path,
458
    irrespective of their status. For such devices, we return this
459
    path, for others we return None.
460

461
    @warning: The path returned is not a normalized pathname; callers
462
        should check that it is a valid path.
463

464
    """
465
    if self.dev_type == constants.LD_LV:
466
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
467
    elif self.dev_type == constants.LD_BLOCKDEV:
468
      return self.logical_id[1]
469
    return None
470

    
471
  def ChildrenNeeded(self):
472
    """Compute the needed number of children for activation.
473

474
    This method will return either -1 (all children) or a positive
475
    number denoting the minimum number of children needed for
476
    activation (only mirrored devices will usually return >=0).
477

478
    Currently, only DRBD8 supports diskless activation (therefore we
479
    return 0), for all other we keep the previous semantics and return
480
    -1.
481

482
    """
483
    if self.dev_type == constants.LD_DRBD8:
484
      return 0
485
    return -1
486

    
487
  def IsBasedOnDiskType(self, dev_type):
488
    """Check if the disk or its children are based on the given type.
489

490
    @type dev_type: L{constants.LDS_BLOCK}
491
    @param dev_type: the type to look for
492
    @rtype: boolean
493
    @return: boolean indicating if a device of the given type was found or not
494

495
    """
496
    if self.children:
497
      for child in self.children:
498
        if child.IsBasedOnDiskType(dev_type):
499
          return True
500
    return self.dev_type == dev_type
501

    
502
  def GetNodes(self, node):
503
    """This function returns the nodes this device lives on.
504

505
    Given the node on which the parent of the device lives on (or, in
506
    case of a top-level device, the primary node of the devices'
507
    instance), this function will return a list of nodes on which this
508
    devices needs to (or can) be assembled.
509

510
    """
511
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
512
                         constants.LD_BLOCKDEV]:
513
      result = [node]
514
    elif self.dev_type in constants.LDS_DRBD:
515
      result = [self.logical_id[0], self.logical_id[1]]
516
      if node not in result:
517
        raise errors.ConfigurationError("DRBD device passed unknown node")
518
    else:
519
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
520
    return result
521

    
522
  def ComputeNodeTree(self, parent_node):
523
    """Compute the node/disk tree for this disk and its children.
524

525
    This method, given the node on which the parent disk lives, will
526
    return the list of all (node, disk) pairs which describe the disk
527
    tree in the most compact way. For example, a drbd/lvm stack
528
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
529
    which represents all the top-level devices on the nodes.
530

531
    """
532
    my_nodes = self.GetNodes(parent_node)
533
    result = [(node, self) for node in my_nodes]
534
    if not self.children:
535
      # leaf device
536
      return result
537
    for node in my_nodes:
538
      for child in self.children:
539
        child_result = child.ComputeNodeTree(node)
540
        if len(child_result) == 1:
541
          # child (and all its descendants) is simple, doesn't split
542
          # over multiple hosts, so we don't need to describe it, our
543
          # own entry for this node describes it completely
544
          continue
545
        else:
546
          # check if child nodes differ from my nodes; note that
547
          # subdisk can differ from the child itself, and be instead
548
          # one of its descendants
549
          for subnode, subdisk in child_result:
550
            if subnode not in my_nodes:
551
              result.append((subnode, subdisk))
552
            # otherwise child is under our own node, so we ignore this
553
            # entry (but probably the other results in the list will
554
            # be different)
555
    return result
556

    
557
  def ComputeGrowth(self, amount):
558
    """Compute the per-VG growth requirements.
559

560
    This only works for VG-based disks.
561

562
    @type amount: integer
563
    @param amount: the desired increase in (user-visible) disk space
564
    @rtype: dict
565
    @return: a dictionary of volume-groups and the required size
566

567
    """
568
    if self.dev_type == constants.LD_LV:
569
      return {self.logical_id[0]: amount}
570
    elif self.dev_type == constants.LD_DRBD8:
571
      if self.children:
572
        return self.children[0].ComputeGrowth(amount)
573
      else:
574
        return {}
575
    else:
576
      # Other disk types do not require VG space
577
      return {}
578

    
579
  def RecordGrow(self, amount):
580
    """Update the size of this disk after growth.
581

582
    This method recurses over the disks's children and updates their
583
    size correspondigly. The method needs to be kept in sync with the
584
    actual algorithms from bdev.
585

586
    """
587
    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
588
      self.size += amount
589
    elif self.dev_type == constants.LD_DRBD8:
590
      if self.children:
591
        self.children[0].RecordGrow(amount)
592
      self.size += amount
593
    else:
594
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
595
                                   " disk type %s" % self.dev_type)
596

    
597
  def UnsetSize(self):
598
    """Sets recursively the size to zero for the disk and its children.
599

600
    """
601
    if self.children:
602
      for child in self.children:
603
        child.UnsetSize()
604
    self.size = 0
605

    
606
  def SetPhysicalID(self, target_node, nodes_ip):
607
    """Convert the logical ID to the physical ID.
608

609
    This is used only for drbd, which needs ip/port configuration.
610

611
    The routine descends down and updates its children also, because
612
    this helps when the only the top device is passed to the remote
613
    node.
614

615
    Arguments:
616
      - target_node: the node we wish to configure for
617
      - nodes_ip: a mapping of node name to ip
618

619
    The target_node must exist in in nodes_ip, and must be one of the
620
    nodes in the logical ID for each of the DRBD devices encountered
621
    in the disk tree.
622

623
    """
624
    if self.children:
625
      for child in self.children:
626
        child.SetPhysicalID(target_node, nodes_ip)
627

    
628
    if self.logical_id is None and self.physical_id is not None:
629
      return
630
    if self.dev_type in constants.LDS_DRBD:
631
      pnode, snode, port, pminor, sminor, secret = self.logical_id
632
      if target_node not in (pnode, snode):
633
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
634
                                        target_node)
635
      pnode_ip = nodes_ip.get(pnode, None)
636
      snode_ip = nodes_ip.get(snode, None)
637
      if pnode_ip is None or snode_ip is None:
638
        raise errors.ConfigurationError("Can't find primary or secondary node"
639
                                        " for %s" % str(self))
640
      p_data = (pnode_ip, port)
641
      s_data = (snode_ip, port)
642
      if pnode == target_node:
643
        self.physical_id = p_data + s_data + (pminor, secret)
644
      else: # it must be secondary, we tested above
645
        self.physical_id = s_data + p_data + (sminor, secret)
646
    else:
647
      self.physical_id = self.logical_id
648
    return
649

    
650
  def ToDict(self):
651
    """Disk-specific conversion to standard python types.
652

653
    This replaces the children lists of objects with lists of
654
    standard python types.
655

656
    """
657
    bo = super(Disk, self).ToDict()
658

    
659
    for attr in ("children",):
660
      alist = bo.get(attr, None)
661
      if alist:
662
        bo[attr] = self._ContainerToDicts(alist)
663
    return bo
664

    
665
  @classmethod
666
  def FromDict(cls, val):
667
    """Custom function for Disks
668

669
    """
670
    obj = super(Disk, cls).FromDict(val)
671
    if obj.children:
672
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
673
    if obj.logical_id and isinstance(obj.logical_id, list):
674
      obj.logical_id = tuple(obj.logical_id)
675
    if obj.physical_id and isinstance(obj.physical_id, list):
676
      obj.physical_id = tuple(obj.physical_id)
677
    if obj.dev_type in constants.LDS_DRBD:
678
      # we need a tuple of length six here
679
      if len(obj.logical_id) < 6:
680
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
681
    return obj
682

    
683
  def __str__(self):
684
    """Custom str() formatter for disks.
685

686
    """
687
    if self.dev_type == constants.LD_LV:
688
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
689
    elif self.dev_type in constants.LDS_DRBD:
690
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
691
      val = "<DRBD8("
692
      if self.physical_id is None:
693
        phy = "unconfigured"
694
      else:
695
        phy = ("configured as %s:%s %s:%s" %
696
               (self.physical_id[0], self.physical_id[1],
697
                self.physical_id[2], self.physical_id[3]))
698

    
699
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
700
              (node_a, minor_a, node_b, minor_b, port, phy))
701
      if self.children and self.children.count(None) == 0:
702
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
703
      else:
704
        val += "no local storage"
705
    else:
706
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
707
             (self.dev_type, self.logical_id, self.physical_id, self.children))
708
    if self.iv_name is None:
709
      val += ", not visible"
710
    else:
711
      val += ", visible as /dev/%s" % self.iv_name
712
    if isinstance(self.size, int):
713
      val += ", size=%dm)>" % self.size
714
    else:
715
      val += ", size='%s')>" % (self.size,)
716
    return val
717

    
718
  def Verify(self):
719
    """Checks that this disk is correctly configured.
720

721
    """
722
    all_errors = []
723
    if self.mode not in constants.DISK_ACCESS_SET:
724
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
725
    return all_errors
726

    
727
  def UpgradeConfig(self):
728
    """Fill defaults for missing configuration values.
729

730
    """
731
    if self.children:
732
      for child in self.children:
733
        child.UpgradeConfig()
734
    # add here config upgrade for this disk
735

    
736

    
737
class Instance(TaggableObject):
738
  """Config object representing an instance."""
739
  __slots__ = [
740
    "name",
741
    "primary_node",
742
    "os",
743
    "hypervisor",
744
    "hvparams",
745
    "beparams",
746
    "osparams",
747
    "admin_up",
748
    "nics",
749
    "disks",
750
    "disk_template",
751
    "network_port",
752
    "serial_no",
753
    ] + _TIMESTAMPS + _UUID
754

    
755
  def _ComputeSecondaryNodes(self):
756
    """Compute the list of secondary nodes.
757

758
    This is a simple wrapper over _ComputeAllNodes.
759

760
    """
761
    all_nodes = set(self._ComputeAllNodes())
762
    all_nodes.discard(self.primary_node)
763
    return tuple(all_nodes)
764

    
765
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
766
                             "List of secondary nodes")
767

    
768
  def _ComputeAllNodes(self):
769
    """Compute the list of all nodes.
770

771
    Since the data is already there (in the drbd disks), keeping it as
772
    a separate normal attribute is redundant and if not properly
773
    synchronised can cause problems. Thus it's better to compute it
774
    dynamically.
775

776
    """
777
    def _Helper(nodes, device):
778
      """Recursively computes nodes given a top device."""
779
      if device.dev_type in constants.LDS_DRBD:
780
        nodea, nodeb = device.logical_id[:2]
781
        nodes.add(nodea)
782
        nodes.add(nodeb)
783
      if device.children:
784
        for child in device.children:
785
          _Helper(nodes, child)
786

    
787
    all_nodes = set()
788
    all_nodes.add(self.primary_node)
789
    for device in self.disks:
790
      _Helper(all_nodes, device)
791
    return tuple(all_nodes)
792

    
793
  all_nodes = property(_ComputeAllNodes, None, None,
794
                       "List of all nodes of the instance")
795

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

799
    This function figures out what logical volumes should belong on
800
    which nodes, recursing through a device tree.
801

802
    @param lvmap: optional dictionary to receive the
803
        'node' : ['lv', ...] data.
804

805
    @return: None if lvmap arg is given, otherwise, a dictionary of
806
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
807
        volumeN is of the form "vg_name/lv_name", compatible with
808
        GetVolumeList()
809

810
    """
811
    if node == None:
812
      node = self.primary_node
813

    
814
    if lvmap is None:
815
      lvmap = {
816
        node: [],
817
        }
818
      ret = lvmap
819
    else:
820
      if not node in lvmap:
821
        lvmap[node] = []
822
      ret = None
823

    
824
    if not devs:
825
      devs = self.disks
826

    
827
    for dev in devs:
828
      if dev.dev_type == constants.LD_LV:
829
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
830

    
831
      elif dev.dev_type in constants.LDS_DRBD:
832
        if dev.children:
833
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
834
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
835

    
836
      elif dev.children:
837
        self.MapLVsByNode(lvmap, dev.children, node)
838

    
839
    return ret
840

    
841
  def FindDisk(self, idx):
842
    """Find a disk given having a specified index.
843

844
    This is just a wrapper that does validation of the index.
845

846
    @type idx: int
847
    @param idx: the disk index
848
    @rtype: L{Disk}
849
    @return: the corresponding disk
850
    @raise errors.OpPrereqError: when the given index is not valid
851

852
    """
853
    try:
854
      idx = int(idx)
855
      return self.disks[idx]
856
    except (TypeError, ValueError), err:
857
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
858
                                 errors.ECODE_INVAL)
859
    except IndexError:
860
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
861
                                 " 0 to %d" % (idx, len(self.disks) - 1),
862
                                 errors.ECODE_INVAL)
863

    
864
  def ToDict(self):
865
    """Instance-specific conversion to standard python types.
866

867
    This replaces the children lists of objects with lists of standard
868
    python types.
869

870
    """
871
    bo = super(Instance, self).ToDict()
872

    
873
    for attr in "nics", "disks":
874
      alist = bo.get(attr, None)
875
      if alist:
876
        nlist = self._ContainerToDicts(alist)
877
      else:
878
        nlist = []
879
      bo[attr] = nlist
880
    return bo
881

    
882
  @classmethod
883
  def FromDict(cls, val):
884
    """Custom function for instances.
885

886
    """
887
    obj = super(Instance, cls).FromDict(val)
888
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
889
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
890
    return obj
891

    
892
  def UpgradeConfig(self):
893
    """Fill defaults for missing configuration values.
894

895
    """
896
    for nic in self.nics:
897
      nic.UpgradeConfig()
898
    for disk in self.disks:
899
      disk.UpgradeConfig()
900
    if self.hvparams:
901
      for key in constants.HVC_GLOBALS:
902
        try:
903
          del self.hvparams[key]
904
        except KeyError:
905
          pass
906
    if self.osparams is None:
907
      self.osparams = {}
908

    
909

    
910
class OS(ConfigObject):
911
  """Config object representing an operating system.
912

913
  @type supported_parameters: list
914
  @ivar supported_parameters: a list of tuples, name and description,
915
      containing the supported parameters by this OS
916

917
  @type VARIANT_DELIM: string
918
  @cvar VARIANT_DELIM: the variant delimiter
919

920
  """
921
  __slots__ = [
922
    "name",
923
    "path",
924
    "api_versions",
925
    "create_script",
926
    "export_script",
927
    "import_script",
928
    "rename_script",
929
    "verify_script",
930
    "supported_variants",
931
    "supported_parameters",
932
    ]
933

    
934
  VARIANT_DELIM = "+"
935

    
936
  @classmethod
937
  def SplitNameVariant(cls, name):
938
    """Splits the name into the proper name and variant.
939

940
    @param name: the OS (unprocessed) name
941
    @rtype: list
942
    @return: a list of two elements; if the original name didn't
943
        contain a variant, it's returned as an empty string
944

945
    """
946
    nv = name.split(cls.VARIANT_DELIM, 1)
947
    if len(nv) == 1:
948
      nv.append("")
949
    return nv
950

    
951
  @classmethod
952
  def GetName(cls, name):
953
    """Returns the proper name of the os (without the variant).
954

955
    @param name: the OS (unprocessed) name
956

957
    """
958
    return cls.SplitNameVariant(name)[0]
959

    
960
  @classmethod
961
  def GetVariant(cls, name):
962
    """Returns the variant the os (without the base name).
963

964
    @param name: the OS (unprocessed) name
965

966
    """
967
    return cls.SplitNameVariant(name)[1]
968

    
969

    
970
class Node(TaggableObject):
971
  """Config object representing a node."""
972
  __slots__ = [
973
    "name",
974
    "primary_ip",
975
    "secondary_ip",
976
    "serial_no",
977
    "master_candidate",
978
    "offline",
979
    "drained",
980
    "group",
981
    "master_capable",
982
    "vm_capable",
983
    "ndparams",
984
    "powered",
985
    "hv_state",
986
    "disk_state",
987
    ] + _TIMESTAMPS + _UUID
988

    
989
  def UpgradeConfig(self):
990
    """Fill defaults for missing configuration values.
991

992
    """
993
    # pylint: disable=E0203
994
    # because these are "defined" via slots, not manually
995
    if self.master_capable is None:
996
      self.master_capable = True
997

    
998
    if self.vm_capable is None:
999
      self.vm_capable = True
1000

    
1001
    if self.ndparams is None:
1002
      self.ndparams = {}
1003

    
1004
    if self.powered is None:
1005
      self.powered = True
1006

    
1007

    
1008
class NodeGroup(TaggableObject):
1009
  """Config object representing a node group."""
1010
  __slots__ = [
1011
    "name",
1012
    "members",
1013
    "ndparams",
1014
    "serial_no",
1015
    "alloc_policy",
1016
    ] + _TIMESTAMPS + _UUID
1017

    
1018
  def ToDict(self):
1019
    """Custom function for nodegroup.
1020

1021
    This discards the members object, which gets recalculated and is only kept
1022
    in memory.
1023

1024
    """
1025
    mydict = super(NodeGroup, self).ToDict()
1026
    del mydict["members"]
1027
    return mydict
1028

    
1029
  @classmethod
1030
  def FromDict(cls, val):
1031
    """Custom function for nodegroup.
1032

1033
    The members slot is initialized to an empty list, upon deserialization.
1034

1035
    """
1036
    obj = super(NodeGroup, cls).FromDict(val)
1037
    obj.members = []
1038
    return obj
1039

    
1040
  def UpgradeConfig(self):
1041
    """Fill defaults for missing configuration values.
1042

1043
    """
1044
    if self.ndparams is None:
1045
      self.ndparams = {}
1046

    
1047
    if self.serial_no is None:
1048
      self.serial_no = 1
1049

    
1050
    if self.alloc_policy is None:
1051
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1052

    
1053
    # We only update mtime, and not ctime, since we would not be able to provide
1054
    # a correct value for creation time.
1055
    if self.mtime is None:
1056
      self.mtime = time.time()
1057

    
1058
  def FillND(self, node):
1059
    """Return filled out ndparams for L{objects.Node}
1060

1061
    @type node: L{objects.Node}
1062
    @param node: A Node object to fill
1063
    @return a copy of the node's ndparams with defaults filled
1064

1065
    """
1066
    return self.SimpleFillND(node.ndparams)
1067

    
1068
  def SimpleFillND(self, ndparams):
1069
    """Fill a given ndparams dict with defaults.
1070

1071
    @type ndparams: dict
1072
    @param ndparams: the dict to fill
1073
    @rtype: dict
1074
    @return: a copy of the passed in ndparams with missing keys filled
1075
        from the node group defaults
1076

1077
    """
1078
    return FillDict(self.ndparams, ndparams)
1079

    
1080

    
1081
class Cluster(TaggableObject):
1082
  """Config object representing the cluster."""
1083
  __slots__ = [
1084
    "serial_no",
1085
    "rsahostkeypub",
1086
    "highest_used_port",
1087
    "tcpudp_port_pool",
1088
    "mac_prefix",
1089
    "volume_group_name",
1090
    "reserved_lvs",
1091
    "drbd_usermode_helper",
1092
    "default_bridge",
1093
    "default_hypervisor",
1094
    "master_node",
1095
    "master_ip",
1096
    "master_netdev",
1097
    "master_netmask",
1098
    "use_external_mip_script",
1099
    "cluster_name",
1100
    "file_storage_dir",
1101
    "shared_file_storage_dir",
1102
    "enabled_hypervisors",
1103
    "hvparams",
1104
    "os_hvp",
1105
    "beparams",
1106
    "osparams",
1107
    "nicparams",
1108
    "ndparams",
1109
    "candidate_pool_size",
1110
    "modify_etc_hosts",
1111
    "modify_ssh_setup",
1112
    "maintain_node_health",
1113
    "uid_pool",
1114
    "default_iallocator",
1115
    "hidden_os",
1116
    "blacklisted_os",
1117
    "primary_ip_family",
1118
    "prealloc_wipe_disks",
1119
    ] + _TIMESTAMPS + _UUID
1120

    
1121
  def UpgradeConfig(self):
1122
    """Fill defaults for missing configuration values.
1123

1124
    """
1125
    # pylint: disable=E0203
1126
    # because these are "defined" via slots, not manually
1127
    if self.hvparams is None:
1128
      self.hvparams = constants.HVC_DEFAULTS
1129
    else:
1130
      for hypervisor in self.hvparams:
1131
        self.hvparams[hypervisor] = FillDict(
1132
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1133

    
1134
    if self.os_hvp is None:
1135
      self.os_hvp = {}
1136

    
1137
    # osparams added before 2.2
1138
    if self.osparams is None:
1139
      self.osparams = {}
1140

    
1141
    if self.ndparams is None:
1142
      self.ndparams = constants.NDC_DEFAULTS
1143

    
1144
    self.beparams = UpgradeGroupedParams(self.beparams,
1145
                                         constants.BEC_DEFAULTS)
1146
    migrate_default_bridge = not self.nicparams
1147
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1148
                                          constants.NICC_DEFAULTS)
1149
    if migrate_default_bridge:
1150
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1151
        self.default_bridge
1152

    
1153
    if self.modify_etc_hosts is None:
1154
      self.modify_etc_hosts = True
1155

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

    
1159
    # default_bridge is no longer used in 2.1. The slot is left there to
1160
    # support auto-upgrading. It can be removed once we decide to deprecate
1161
    # upgrading straight from 2.0.
1162
    if self.default_bridge is not None:
1163
      self.default_bridge = None
1164

    
1165
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1166
    # code can be removed once upgrading straight from 2.0 is deprecated.
1167
    if self.default_hypervisor is not None:
1168
      self.enabled_hypervisors = ([self.default_hypervisor] +
1169
        [hvname for hvname in self.enabled_hypervisors
1170
         if hvname != self.default_hypervisor])
1171
      self.default_hypervisor = None
1172

    
1173
    # maintain_node_health added after 2.1.1
1174
    if self.maintain_node_health is None:
1175
      self.maintain_node_health = False
1176

    
1177
    if self.uid_pool is None:
1178
      self.uid_pool = []
1179

    
1180
    if self.default_iallocator is None:
1181
      self.default_iallocator = ""
1182

    
1183
    # reserved_lvs added before 2.2
1184
    if self.reserved_lvs is None:
1185
      self.reserved_lvs = []
1186

    
1187
    # hidden and blacklisted operating systems added before 2.2.1
1188
    if self.hidden_os is None:
1189
      self.hidden_os = []
1190

    
1191
    if self.blacklisted_os is None:
1192
      self.blacklisted_os = []
1193

    
1194
    # primary_ip_family added before 2.3
1195
    if self.primary_ip_family is None:
1196
      self.primary_ip_family = AF_INET
1197

    
1198
    if self.master_netmask is None:
1199
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1200
      self.master_netmask = ipcls.iplen
1201

    
1202
    if self.prealloc_wipe_disks is None:
1203
      self.prealloc_wipe_disks = False
1204

    
1205
    # shared_file_storage_dir added before 2.5
1206
    if self.shared_file_storage_dir is None:
1207
      self.shared_file_storage_dir = ""
1208

    
1209
    if self.use_external_mip_script is None:
1210
      self.use_external_mip_script = False
1211

    
1212
  def ToDict(self):
1213
    """Custom function for cluster.
1214

1215
    """
1216
    mydict = super(Cluster, self).ToDict()
1217
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1218
    return mydict
1219

    
1220
  @classmethod
1221
  def FromDict(cls, val):
1222
    """Custom function for cluster.
1223

1224
    """
1225
    obj = super(Cluster, cls).FromDict(val)
1226
    if not isinstance(obj.tcpudp_port_pool, set):
1227
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1228
    return obj
1229

    
1230
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1231
    """Get the default hypervisor parameters for the cluster.
1232

1233
    @param hypervisor: the hypervisor name
1234
    @param os_name: if specified, we'll also update the defaults for this OS
1235
    @param skip_keys: if passed, list of keys not to use
1236
    @return: the defaults dict
1237

1238
    """
1239
    if skip_keys is None:
1240
      skip_keys = []
1241

    
1242
    fill_stack = [self.hvparams.get(hypervisor, {})]
1243
    if os_name is not None:
1244
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1245
      fill_stack.append(os_hvp)
1246

    
1247
    ret_dict = {}
1248
    for o_dict in fill_stack:
1249
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1250

    
1251
    return ret_dict
1252

    
1253
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1254
    """Fill a given hvparams dict with cluster defaults.
1255

1256
    @type hv_name: string
1257
    @param hv_name: the hypervisor to use
1258
    @type os_name: string
1259
    @param os_name: the OS to use for overriding the hypervisor defaults
1260
    @type skip_globals: boolean
1261
    @param skip_globals: if True, the global hypervisor parameters will
1262
        not be filled
1263
    @rtype: dict
1264
    @return: a copy of the given hvparams with missing keys filled from
1265
        the cluster defaults
1266

1267
    """
1268
    if skip_globals:
1269
      skip_keys = constants.HVC_GLOBALS
1270
    else:
1271
      skip_keys = []
1272

    
1273
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1274
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1275

    
1276
  def FillHV(self, instance, skip_globals=False):
1277
    """Fill an instance's hvparams dict with cluster defaults.
1278

1279
    @type instance: L{objects.Instance}
1280
    @param instance: the instance parameter to fill
1281
    @type skip_globals: boolean
1282
    @param skip_globals: if True, the global hypervisor parameters will
1283
        not be filled
1284
    @rtype: dict
1285
    @return: a copy of the instance's hvparams with missing keys filled from
1286
        the cluster defaults
1287

1288
    """
1289
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1290
                             instance.hvparams, skip_globals)
1291

    
1292
  def SimpleFillBE(self, beparams):
1293
    """Fill a given beparams dict with cluster defaults.
1294

1295
    @type beparams: dict
1296
    @param beparams: the dict to fill
1297
    @rtype: dict
1298
    @return: a copy of the passed in beparams with missing keys filled
1299
        from the cluster defaults
1300

1301
    """
1302
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1303

    
1304
  def FillBE(self, instance):
1305
    """Fill an instance's beparams dict with cluster defaults.
1306

1307
    @type instance: L{objects.Instance}
1308
    @param instance: the instance parameter to fill
1309
    @rtype: dict
1310
    @return: a copy of the instance's beparams with missing keys filled from
1311
        the cluster defaults
1312

1313
    """
1314
    return self.SimpleFillBE(instance.beparams)
1315

    
1316
  def SimpleFillNIC(self, nicparams):
1317
    """Fill a given nicparams dict with cluster defaults.
1318

1319
    @type nicparams: dict
1320
    @param nicparams: the dict to fill
1321
    @rtype: dict
1322
    @return: a copy of the passed in nicparams with missing keys filled
1323
        from the cluster defaults
1324

1325
    """
1326
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1327

    
1328
  def SimpleFillOS(self, os_name, os_params):
1329
    """Fill an instance's osparams dict with cluster defaults.
1330

1331
    @type os_name: string
1332
    @param os_name: the OS name to use
1333
    @type os_params: dict
1334
    @param os_params: the dict to fill with default values
1335
    @rtype: dict
1336
    @return: a copy of the instance's osparams with missing keys filled from
1337
        the cluster defaults
1338

1339
    """
1340
    name_only = os_name.split("+", 1)[0]
1341
    # base OS
1342
    result = self.osparams.get(name_only, {})
1343
    # OS with variant
1344
    result = FillDict(result, self.osparams.get(os_name, {}))
1345
    # specified params
1346
    return FillDict(result, os_params)
1347

    
1348
  def FillND(self, node, nodegroup):
1349
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1350

1351
    @type node: L{objects.Node}
1352
    @param node: A Node object to fill
1353
    @type nodegroup: L{objects.NodeGroup}
1354
    @param nodegroup: A Node object to fill
1355
    @return a copy of the node's ndparams with defaults filled
1356

1357
    """
1358
    return self.SimpleFillND(nodegroup.FillND(node))
1359

    
1360
  def SimpleFillND(self, ndparams):
1361
    """Fill a given ndparams dict with defaults.
1362

1363
    @type ndparams: dict
1364
    @param ndparams: the dict to fill
1365
    @rtype: dict
1366
    @return: a copy of the passed in ndparams with missing keys filled
1367
        from the cluster defaults
1368

1369
    """
1370
    return FillDict(self.ndparams, ndparams)
1371

    
1372

    
1373
class BlockDevStatus(ConfigObject):
1374
  """Config object representing the status of a block device."""
1375
  __slots__ = [
1376
    "dev_path",
1377
    "major",
1378
    "minor",
1379
    "sync_percent",
1380
    "estimated_time",
1381
    "is_degraded",
1382
    "ldisk_status",
1383
    ]
1384

    
1385

    
1386
class ImportExportStatus(ConfigObject):
1387
  """Config object representing the status of an import or export."""
1388
  __slots__ = [
1389
    "recent_output",
1390
    "listen_port",
1391
    "connected",
1392
    "progress_mbytes",
1393
    "progress_throughput",
1394
    "progress_eta",
1395
    "progress_percent",
1396
    "exit_status",
1397
    "error_message",
1398
    ] + _TIMESTAMPS
1399

    
1400

    
1401
class ImportExportOptions(ConfigObject):
1402
  """Options for import/export daemon
1403

1404
  @ivar key_name: X509 key name (None for cluster certificate)
1405
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1406
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1407
  @ivar magic: Used to ensure the connection goes to the right disk
1408
  @ivar ipv6: Whether to use IPv6
1409
  @ivar connect_timeout: Number of seconds for establishing connection
1410

1411
  """
1412
  __slots__ = [
1413
    "key_name",
1414
    "ca_pem",
1415
    "compress",
1416
    "magic",
1417
    "ipv6",
1418
    "connect_timeout",
1419
    ]
1420

    
1421

    
1422
class ConfdRequest(ConfigObject):
1423
  """Object holding a confd request.
1424

1425
  @ivar protocol: confd protocol version
1426
  @ivar type: confd query type
1427
  @ivar query: query request
1428
  @ivar rsalt: requested reply salt
1429

1430
  """
1431
  __slots__ = [
1432
    "protocol",
1433
    "type",
1434
    "query",
1435
    "rsalt",
1436
    ]
1437

    
1438

    
1439
class ConfdReply(ConfigObject):
1440
  """Object holding a confd reply.
1441

1442
  @ivar protocol: confd protocol version
1443
  @ivar status: reply status code (ok, error)
1444
  @ivar answer: confd query reply
1445
  @ivar serial: configuration serial number
1446

1447
  """
1448
  __slots__ = [
1449
    "protocol",
1450
    "status",
1451
    "answer",
1452
    "serial",
1453
    ]
1454

    
1455

    
1456
class QueryFieldDefinition(ConfigObject):
1457
  """Object holding a query field definition.
1458

1459
  @ivar name: Field name
1460
  @ivar title: Human-readable title
1461
  @ivar kind: Field type
1462
  @ivar doc: Human-readable description
1463

1464
  """
1465
  __slots__ = [
1466
    "name",
1467
    "title",
1468
    "kind",
1469
    "doc",
1470
    ]
1471

    
1472

    
1473
class _QueryResponseBase(ConfigObject):
1474
  __slots__ = [
1475
    "fields",
1476
    ]
1477

    
1478
  def ToDict(self):
1479
    """Custom function for serializing.
1480

1481
    """
1482
    mydict = super(_QueryResponseBase, self).ToDict()
1483
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1484
    return mydict
1485

    
1486
  @classmethod
1487
  def FromDict(cls, val):
1488
    """Custom function for de-serializing.
1489

1490
    """
1491
    obj = super(_QueryResponseBase, cls).FromDict(val)
1492
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1493
    return obj
1494

    
1495

    
1496
class QueryRequest(ConfigObject):
1497
  """Object holding a query request.
1498

1499
  """
1500
  __slots__ = [
1501
    "what",
1502
    "fields",
1503
    "qfilter",
1504
    ]
1505

    
1506

    
1507
class QueryResponse(_QueryResponseBase):
1508
  """Object holding the response to a query.
1509

1510
  @ivar fields: List of L{QueryFieldDefinition} objects
1511
  @ivar data: Requested data
1512

1513
  """
1514
  __slots__ = [
1515
    "data",
1516
    ]
1517

    
1518

    
1519
class QueryFieldsRequest(ConfigObject):
1520
  """Object holding a request for querying available fields.
1521

1522
  """
1523
  __slots__ = [
1524
    "what",
1525
    "fields",
1526
    ]
1527

    
1528

    
1529
class QueryFieldsResponse(_QueryResponseBase):
1530
  """Object holding the response to a query for fields.
1531

1532
  @ivar fields: List of L{QueryFieldDefinition} objects
1533

1534
  """
1535
  __slots__ = [
1536
    ]
1537

    
1538

    
1539
class MigrationStatus(ConfigObject):
1540
  """Object holding the status of a migration.
1541

1542
  """
1543
  __slots__ = [
1544
    "status",
1545
    "transferred_ram",
1546
    "total_ram",
1547
    ]
1548

    
1549

    
1550
class InstanceConsole(ConfigObject):
1551
  """Object describing how to access the console of an instance.
1552

1553
  """
1554
  __slots__ = [
1555
    "instance",
1556
    "kind",
1557
    "message",
1558
    "host",
1559
    "port",
1560
    "user",
1561
    "command",
1562
    "display",
1563
    ]
1564

    
1565
  def Validate(self):
1566
    """Validates contents of this object.
1567

1568
    """
1569
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1570
    assert self.instance, "Missing instance name"
1571
    assert self.message or self.kind in [constants.CONS_SSH,
1572
                                         constants.CONS_SPICE,
1573
                                         constants.CONS_VNC]
1574
    assert self.host or self.kind == constants.CONS_MESSAGE
1575
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1576
                                      constants.CONS_SSH]
1577
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1578
                                      constants.CONS_SPICE,
1579
                                      constants.CONS_VNC]
1580
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1581
                                         constants.CONS_SPICE,
1582
                                         constants.CONS_VNC]
1583
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1584
                                         constants.CONS_SPICE,
1585
                                         constants.CONS_SSH]
1586
    return True
1587

    
1588

    
1589
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1590
  """Simple wrapper over ConfigParse that allows serialization.
1591

1592
  This class is basically ConfigParser.SafeConfigParser with two
1593
  additional methods that allow it to serialize/unserialize to/from a
1594
  buffer.
1595

1596
  """
1597
  def Dumps(self):
1598
    """Dump this instance and return the string representation."""
1599
    buf = StringIO()
1600
    self.write(buf)
1601
    return buf.getvalue()
1602

    
1603
  @classmethod
1604
  def Loads(cls, data):
1605
    """Load data from a string."""
1606
    buf = StringIO(data)
1607
    cfp = cls()
1608
    cfp.readfp(buf)
1609
    return cfp