Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 861610e9

History | View | Annotate | Download (45.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 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
def UpgradeBeParams(target):
100
  """Update the be parameters dict to the new format.
101

102
  @type target: dict
103
  @param target: "be" parameters dict
104

105
  """
106
  if constants.BE_MEMORY in target:
107
    memory = target[constants.BE_MEMORY]
108
    target[constants.BE_MAXMEM] = memory
109
    target[constants.BE_MINMEM] = memory
110
    #FIXME(dynmem): delete old value
111
    #del target[constants.BE_MEMORY]
112

    
113

    
114
class ConfigObject(object):
115
  """A generic config object.
116

117
  It has the following properties:
118

119
    - provides somewhat safe recursive unpickling and pickling for its classes
120
    - unset attributes which are defined in slots are always returned
121
      as None instead of raising an error
122

123
  Classes derived from this must always declare __slots__ (we use many
124
  config objects and the memory reduction is useful)
125

126
  """
127
  __slots__ = []
128

    
129
  def __init__(self, **kwargs):
130
    for k, v in kwargs.iteritems():
131
      setattr(self, k, v)
132

    
133
  def __getattr__(self, name):
134
    if name not in self._all_slots():
135
      raise AttributeError("Invalid object attribute %s.%s" %
136
                           (type(self).__name__, name))
137
    return None
138

    
139
  def __setstate__(self, state):
140
    slots = self._all_slots()
141
    for name in state:
142
      if name in slots:
143
        setattr(self, name, state[name])
144

    
145
  @classmethod
146
  def _all_slots(cls):
147
    """Compute the list of all declared slots for a class.
148

149
    """
150
    slots = []
151
    for parent in cls.__mro__:
152
      slots.extend(getattr(parent, "__slots__", []))
153
    return slots
154

    
155
  def ToDict(self):
156
    """Convert to a dict holding only standard python types.
157

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

164
    """
165
    result = {}
166
    for name in self._all_slots():
167
      value = getattr(self, name, None)
168
      if value is not None:
169
        result[name] = value
170
    return result
171

    
172
  __getstate__ = ToDict
173

    
174
  @classmethod
175
  def FromDict(cls, val):
176
    """Create an object from a dictionary.
177

178
    This generic routine takes a dict, instantiates a new instance of
179
    the given class, and sets attributes based on the dict content.
180

181
    As for `ToDict`, this does not work if the class has children
182
    who are ConfigObjects themselves (e.g. the nics list in an
183
    Instance), in which case the object should subclass the function
184
    and alter the objects.
185

186
    """
187
    if not isinstance(val, dict):
188
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
189
                                      " expected dict, got %s" % type(val))
190
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
191
    obj = cls(**val_str) # pylint: disable=W0142
192
    return obj
193

    
194
  @staticmethod
195
  def _ContainerToDicts(container):
196
    """Convert the elements of a container to standard python types.
197

198
    This method converts a container with elements derived from
199
    ConfigData to standard python types. If the container is a dict,
200
    we don't touch the keys, only the values.
201

202
    """
203
    if isinstance(container, dict):
204
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
205
    elif isinstance(container, (list, tuple, set, frozenset)):
206
      ret = [elem.ToDict() for elem in container]
207
    else:
208
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
209
                      type(container))
210
    return ret
211

    
212
  @staticmethod
213
  def _ContainerFromDicts(source, c_type, e_type):
214
    """Convert a container from standard python types.
215

216
    This method converts a container with standard python types to
217
    ConfigData objects. If the container is a dict, we don't touch the
218
    keys, only the values.
219

220
    """
221
    if not isinstance(c_type, type):
222
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
223
                      " not a type" % type(c_type))
224
    if source is None:
225
      source = c_type()
226
    if c_type is dict:
227
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
228
    elif c_type in (list, tuple, set, frozenset):
229
      ret = c_type([e_type.FromDict(elem) for elem in source])
230
    else:
231
      raise TypeError("Invalid container type %s passed to"
232
                      " _ContainerFromDicts" % c_type)
233
    return ret
234

    
235
  def Copy(self):
236
    """Makes a deep copy of the current object and its children.
237

238
    """
239
    dict_form = self.ToDict()
240
    clone_obj = self.__class__.FromDict(dict_form)
241
    return clone_obj
242

    
243
  def __repr__(self):
244
    """Implement __repr__ for ConfigObjects."""
245
    return repr(self.ToDict())
246

    
247
  def UpgradeConfig(self):
248
    """Fill defaults for missing configuration values.
249

250
    This method will be called at configuration load time, and its
251
    implementation will be object dependent.
252

253
    """
254
    pass
255

    
256

    
257
class TaggableObject(ConfigObject):
258
  """An generic class supporting tags.
259

260
  """
261
  __slots__ = ["tags"]
262
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
263

    
264
  @classmethod
265
  def ValidateTag(cls, tag):
266
    """Check if a tag is valid.
267

268
    If the tag is invalid, an errors.TagError will be raised. The
269
    function has no return value.
270

271
    """
272
    if not isinstance(tag, basestring):
273
      raise errors.TagError("Invalid tag type (not a string)")
274
    if len(tag) > constants.MAX_TAG_LEN:
275
      raise errors.TagError("Tag too long (>%d characters)" %
276
                            constants.MAX_TAG_LEN)
277
    if not tag:
278
      raise errors.TagError("Tags cannot be empty")
279
    if not cls.VALID_TAG_RE.match(tag):
280
      raise errors.TagError("Tag contains invalid characters")
281

    
282
  def GetTags(self):
283
    """Return the tags list.
284

285
    """
286
    tags = getattr(self, "tags", None)
287
    if tags is None:
288
      tags = self.tags = set()
289
    return tags
290

    
291
  def AddTag(self, tag):
292
    """Add a new tag.
293

294
    """
295
    self.ValidateTag(tag)
296
    tags = self.GetTags()
297
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
298
      raise errors.TagError("Too many tags")
299
    self.GetTags().add(tag)
300

    
301
  def RemoveTag(self, tag):
302
    """Remove a tag.
303

304
    """
305
    self.ValidateTag(tag)
306
    tags = self.GetTags()
307
    try:
308
      tags.remove(tag)
309
    except KeyError:
310
      raise errors.TagError("Tag not found")
311

    
312
  def ToDict(self):
313
    """Taggable-object-specific conversion to standard python types.
314

315
    This replaces the tags set with a list.
316

317
    """
318
    bo = super(TaggableObject, self).ToDict()
319

    
320
    tags = bo.get("tags", None)
321
    if isinstance(tags, set):
322
      bo["tags"] = list(tags)
323
    return bo
324

    
325
  @classmethod
326
  def FromDict(cls, val):
327
    """Custom function for instances.
328

329
    """
330
    obj = super(TaggableObject, cls).FromDict(val)
331
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
332
      obj.tags = set(obj.tags)
333
    return obj
334

    
335

    
336
class MasterNetworkParameters(ConfigObject):
337
  """Network configuration parameters for the master
338

339
  @ivar name: master name
340
  @ivar ip: master IP
341
  @ivar netmask: master netmask
342
  @ivar netdev: master network device
343
  @ivar ip_family: master IP family
344

345
  """
346
  __slots__ = [
347
    "name",
348
    "ip",
349
    "netmask",
350
    "netdev",
351
    "ip_family"
352
    ]
353

    
354

    
355
class ConfigData(ConfigObject):
356
  """Top-level config object."""
357
  __slots__ = [
358
    "version",
359
    "cluster",
360
    "nodes",
361
    "nodegroups",
362
    "instances",
363
    "serial_no",
364
    ] + _TIMESTAMPS
365

    
366
  def ToDict(self):
367
    """Custom function for top-level config data.
368

369
    This just replaces the list of instances, nodes and the cluster
370
    with standard python types.
371

372
    """
373
    mydict = super(ConfigData, self).ToDict()
374
    mydict["cluster"] = mydict["cluster"].ToDict()
375
    for key in "nodes", "instances", "nodegroups":
376
      mydict[key] = self._ContainerToDicts(mydict[key])
377

    
378
    return mydict
379

    
380
  @classmethod
381
  def FromDict(cls, val):
382
    """Custom function for top-level config data
383

384
    """
385
    obj = super(ConfigData, cls).FromDict(val)
386
    obj.cluster = Cluster.FromDict(obj.cluster)
387
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
388
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
389
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
390
    return obj
391

    
392
  def HasAnyDiskOfType(self, dev_type):
393
    """Check if in there is at disk of the given type in the configuration.
394

395
    @type dev_type: L{constants.LDS_BLOCK}
396
    @param dev_type: the type to look for
397
    @rtype: boolean
398
    @return: boolean indicating if a disk of the given type was found or not
399

400
    """
401
    for instance in self.instances.values():
402
      for disk in instance.disks:
403
        if disk.IsBasedOnDiskType(dev_type):
404
          return True
405
    return False
406

    
407
  def UpgradeConfig(self):
408
    """Fill defaults for missing configuration values.
409

410
    """
411
    self.cluster.UpgradeConfig()
412
    for node in self.nodes.values():
413
      node.UpgradeConfig()
414
    for instance in self.instances.values():
415
      instance.UpgradeConfig()
416
    if self.nodegroups is None:
417
      self.nodegroups = {}
418
    for nodegroup in self.nodegroups.values():
419
      nodegroup.UpgradeConfig()
420
    if self.cluster.drbd_usermode_helper is None:
421
      # To decide if we set an helper let's check if at least one instance has
422
      # a DRBD disk. This does not cover all the possible scenarios but it
423
      # gives a good approximation.
424
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
425
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
426

    
427

    
428
class NIC(ConfigObject):
429
  """Config object representing a network card."""
430
  __slots__ = ["mac", "ip", "nicparams"]
431

    
432
  @classmethod
433
  def CheckParameterSyntax(cls, nicparams):
434
    """Check the given parameters for validity.
435

436
    @type nicparams:  dict
437
    @param nicparams: dictionary with parameter names/value
438
    @raise errors.ConfigurationError: when a parameter is not valid
439

440
    """
441
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
442
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
443
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
444
      raise errors.ConfigurationError(err)
445

    
446
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
447
        not nicparams[constants.NIC_LINK]):
448
      err = "Missing bridged nic link"
449
      raise errors.ConfigurationError(err)
450

    
451

    
452
class Disk(ConfigObject):
453
  """Config object representing a block device."""
454
  __slots__ = ["dev_type", "logical_id", "physical_id",
455
               "children", "iv_name", "size", "mode"]
456

    
457
  def CreateOnSecondary(self):
458
    """Test if this device needs to be created on a secondary node."""
459
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
460

    
461
  def AssembleOnSecondary(self):
462
    """Test if this device needs to be assembled on a secondary node."""
463
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
464

    
465
  def OpenOnSecondary(self):
466
    """Test if this device needs to be opened on a secondary node."""
467
    return self.dev_type in (constants.LD_LV,)
468

    
469
  def StaticDevPath(self):
470
    """Return the device path if this device type has a static one.
471

472
    Some devices (LVM for example) live always at the same /dev/ path,
473
    irrespective of their status. For such devices, we return this
474
    path, for others we return None.
475

476
    @warning: The path returned is not a normalized pathname; callers
477
        should check that it is a valid path.
478

479
    """
480
    if self.dev_type == constants.LD_LV:
481
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
482
    elif self.dev_type == constants.LD_BLOCKDEV:
483
      return self.logical_id[1]
484
    return None
485

    
486
  def ChildrenNeeded(self):
487
    """Compute the needed number of children for activation.
488

489
    This method will return either -1 (all children) or a positive
490
    number denoting the minimum number of children needed for
491
    activation (only mirrored devices will usually return >=0).
492

493
    Currently, only DRBD8 supports diskless activation (therefore we
494
    return 0), for all other we keep the previous semantics and return
495
    -1.
496

497
    """
498
    if self.dev_type == constants.LD_DRBD8:
499
      return 0
500
    return -1
501

    
502
  def IsBasedOnDiskType(self, dev_type):
503
    """Check if the disk or its children are based on the given type.
504

505
    @type dev_type: L{constants.LDS_BLOCK}
506
    @param dev_type: the type to look for
507
    @rtype: boolean
508
    @return: boolean indicating if a device of the given type was found or not
509

510
    """
511
    if self.children:
512
      for child in self.children:
513
        if child.IsBasedOnDiskType(dev_type):
514
          return True
515
    return self.dev_type == dev_type
516

    
517
  def GetNodes(self, node):
518
    """This function returns the nodes this device lives on.
519

520
    Given the node on which the parent of the device lives on (or, in
521
    case of a top-level device, the primary node of the devices'
522
    instance), this function will return a list of nodes on which this
523
    devices needs to (or can) be assembled.
524

525
    """
526
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
527
                         constants.LD_BLOCKDEV]:
528
      result = [node]
529
    elif self.dev_type in constants.LDS_DRBD:
530
      result = [self.logical_id[0], self.logical_id[1]]
531
      if node not in result:
532
        raise errors.ConfigurationError("DRBD device passed unknown node")
533
    else:
534
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
535
    return result
536

    
537
  def ComputeNodeTree(self, parent_node):
538
    """Compute the node/disk tree for this disk and its children.
539

540
    This method, given the node on which the parent disk lives, will
541
    return the list of all (node, disk) pairs which describe the disk
542
    tree in the most compact way. For example, a drbd/lvm stack
543
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
544
    which represents all the top-level devices on the nodes.
545

546
    """
547
    my_nodes = self.GetNodes(parent_node)
548
    result = [(node, self) for node in my_nodes]
549
    if not self.children:
550
      # leaf device
551
      return result
552
    for node in my_nodes:
553
      for child in self.children:
554
        child_result = child.ComputeNodeTree(node)
555
        if len(child_result) == 1:
556
          # child (and all its descendants) is simple, doesn't split
557
          # over multiple hosts, so we don't need to describe it, our
558
          # own entry for this node describes it completely
559
          continue
560
        else:
561
          # check if child nodes differ from my nodes; note that
562
          # subdisk can differ from the child itself, and be instead
563
          # one of its descendants
564
          for subnode, subdisk in child_result:
565
            if subnode not in my_nodes:
566
              result.append((subnode, subdisk))
567
            # otherwise child is under our own node, so we ignore this
568
            # entry (but probably the other results in the list will
569
            # be different)
570
    return result
571

    
572
  def ComputeGrowth(self, amount):
573
    """Compute the per-VG growth requirements.
574

575
    This only works for VG-based disks.
576

577
    @type amount: integer
578
    @param amount: the desired increase in (user-visible) disk space
579
    @rtype: dict
580
    @return: a dictionary of volume-groups and the required size
581

582
    """
583
    if self.dev_type == constants.LD_LV:
584
      return {self.logical_id[0]: amount}
585
    elif self.dev_type == constants.LD_DRBD8:
586
      if self.children:
587
        return self.children[0].ComputeGrowth(amount)
588
      else:
589
        return {}
590
    else:
591
      # Other disk types do not require VG space
592
      return {}
593

    
594
  def RecordGrow(self, amount):
595
    """Update the size of this disk after growth.
596

597
    This method recurses over the disks's children and updates their
598
    size correspondigly. The method needs to be kept in sync with the
599
    actual algorithms from bdev.
600

601
    """
602
    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
603
      self.size += amount
604
    elif self.dev_type == constants.LD_DRBD8:
605
      if self.children:
606
        self.children[0].RecordGrow(amount)
607
      self.size += amount
608
    else:
609
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
610
                                   " disk type %s" % self.dev_type)
611

    
612
  def UnsetSize(self):
613
    """Sets recursively the size to zero for the disk and its children.
614

615
    """
616
    if self.children:
617
      for child in self.children:
618
        child.UnsetSize()
619
    self.size = 0
620

    
621
  def SetPhysicalID(self, target_node, nodes_ip):
622
    """Convert the logical ID to the physical ID.
623

624
    This is used only for drbd, which needs ip/port configuration.
625

626
    The routine descends down and updates its children also, because
627
    this helps when the only the top device is passed to the remote
628
    node.
629

630
    Arguments:
631
      - target_node: the node we wish to configure for
632
      - nodes_ip: a mapping of node name to ip
633

634
    The target_node must exist in in nodes_ip, and must be one of the
635
    nodes in the logical ID for each of the DRBD devices encountered
636
    in the disk tree.
637

638
    """
639
    if self.children:
640
      for child in self.children:
641
        child.SetPhysicalID(target_node, nodes_ip)
642

    
643
    if self.logical_id is None and self.physical_id is not None:
644
      return
645
    if self.dev_type in constants.LDS_DRBD:
646
      pnode, snode, port, pminor, sminor, secret = self.logical_id
647
      if target_node not in (pnode, snode):
648
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
649
                                        target_node)
650
      pnode_ip = nodes_ip.get(pnode, None)
651
      snode_ip = nodes_ip.get(snode, None)
652
      if pnode_ip is None or snode_ip is None:
653
        raise errors.ConfigurationError("Can't find primary or secondary node"
654
                                        " for %s" % str(self))
655
      p_data = (pnode_ip, port)
656
      s_data = (snode_ip, port)
657
      if pnode == target_node:
658
        self.physical_id = p_data + s_data + (pminor, secret)
659
      else: # it must be secondary, we tested above
660
        self.physical_id = s_data + p_data + (sminor, secret)
661
    else:
662
      self.physical_id = self.logical_id
663
    return
664

    
665
  def ToDict(self):
666
    """Disk-specific conversion to standard python types.
667

668
    This replaces the children lists of objects with lists of
669
    standard python types.
670

671
    """
672
    bo = super(Disk, self).ToDict()
673

    
674
    for attr in ("children",):
675
      alist = bo.get(attr, None)
676
      if alist:
677
        bo[attr] = self._ContainerToDicts(alist)
678
    return bo
679

    
680
  @classmethod
681
  def FromDict(cls, val):
682
    """Custom function for Disks
683

684
    """
685
    obj = super(Disk, cls).FromDict(val)
686
    if obj.children:
687
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
688
    if obj.logical_id and isinstance(obj.logical_id, list):
689
      obj.logical_id = tuple(obj.logical_id)
690
    if obj.physical_id and isinstance(obj.physical_id, list):
691
      obj.physical_id = tuple(obj.physical_id)
692
    if obj.dev_type in constants.LDS_DRBD:
693
      # we need a tuple of length six here
694
      if len(obj.logical_id) < 6:
695
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
696
    return obj
697

    
698
  def __str__(self):
699
    """Custom str() formatter for disks.
700

701
    """
702
    if self.dev_type == constants.LD_LV:
703
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
704
    elif self.dev_type in constants.LDS_DRBD:
705
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
706
      val = "<DRBD8("
707
      if self.physical_id is None:
708
        phy = "unconfigured"
709
      else:
710
        phy = ("configured as %s:%s %s:%s" %
711
               (self.physical_id[0], self.physical_id[1],
712
                self.physical_id[2], self.physical_id[3]))
713

    
714
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
715
              (node_a, minor_a, node_b, minor_b, port, phy))
716
      if self.children and self.children.count(None) == 0:
717
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
718
      else:
719
        val += "no local storage"
720
    else:
721
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
722
             (self.dev_type, self.logical_id, self.physical_id, self.children))
723
    if self.iv_name is None:
724
      val += ", not visible"
725
    else:
726
      val += ", visible as /dev/%s" % self.iv_name
727
    if isinstance(self.size, int):
728
      val += ", size=%dm)>" % self.size
729
    else:
730
      val += ", size='%s')>" % (self.size,)
731
    return val
732

    
733
  def Verify(self):
734
    """Checks that this disk is correctly configured.
735

736
    """
737
    all_errors = []
738
    if self.mode not in constants.DISK_ACCESS_SET:
739
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
740
    return all_errors
741

    
742
  def UpgradeConfig(self):
743
    """Fill defaults for missing configuration values.
744

745
    """
746
    if self.children:
747
      for child in self.children:
748
        child.UpgradeConfig()
749
    # add here config upgrade for this disk
750

    
751

    
752
class Instance(TaggableObject):
753
  """Config object representing an instance."""
754
  __slots__ = [
755
    "name",
756
    "primary_node",
757
    "os",
758
    "hypervisor",
759
    "hvparams",
760
    "beparams",
761
    "osparams",
762
    "admin_state",
763
    "nics",
764
    "disks",
765
    "disk_template",
766
    "network_port",
767
    "serial_no",
768
    ] + _TIMESTAMPS + _UUID
769

    
770
  def _ComputeSecondaryNodes(self):
771
    """Compute the list of secondary nodes.
772

773
    This is a simple wrapper over _ComputeAllNodes.
774

775
    """
776
    all_nodes = set(self._ComputeAllNodes())
777
    all_nodes.discard(self.primary_node)
778
    return tuple(all_nodes)
779

    
780
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
781
                             "List of secondary nodes")
782

    
783
  def _ComputeAllNodes(self):
784
    """Compute the list of all nodes.
785

786
    Since the data is already there (in the drbd disks), keeping it as
787
    a separate normal attribute is redundant and if not properly
788
    synchronised can cause problems. Thus it's better to compute it
789
    dynamically.
790

791
    """
792
    def _Helper(nodes, device):
793
      """Recursively computes nodes given a top device."""
794
      if device.dev_type in constants.LDS_DRBD:
795
        nodea, nodeb = device.logical_id[:2]
796
        nodes.add(nodea)
797
        nodes.add(nodeb)
798
      if device.children:
799
        for child in device.children:
800
          _Helper(nodes, child)
801

    
802
    all_nodes = set()
803
    all_nodes.add(self.primary_node)
804
    for device in self.disks:
805
      _Helper(all_nodes, device)
806
    return tuple(all_nodes)
807

    
808
  all_nodes = property(_ComputeAllNodes, None, None,
809
                       "List of all nodes of the instance")
810

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

814
    This function figures out what logical volumes should belong on
815
    which nodes, recursing through a device tree.
816

817
    @param lvmap: optional dictionary to receive the
818
        'node' : ['lv', ...] data.
819

820
    @return: None if lvmap arg is given, otherwise, a dictionary of
821
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
822
        volumeN is of the form "vg_name/lv_name", compatible with
823
        GetVolumeList()
824

825
    """
826
    if node == None:
827
      node = self.primary_node
828

    
829
    if lvmap is None:
830
      lvmap = {
831
        node: [],
832
        }
833
      ret = lvmap
834
    else:
835
      if not node in lvmap:
836
        lvmap[node] = []
837
      ret = None
838

    
839
    if not devs:
840
      devs = self.disks
841

    
842
    for dev in devs:
843
      if dev.dev_type == constants.LD_LV:
844
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
845

    
846
      elif dev.dev_type in constants.LDS_DRBD:
847
        if dev.children:
848
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
849
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
850

    
851
      elif dev.children:
852
        self.MapLVsByNode(lvmap, dev.children, node)
853

    
854
    return ret
855

    
856
  def FindDisk(self, idx):
857
    """Find a disk given having a specified index.
858

859
    This is just a wrapper that does validation of the index.
860

861
    @type idx: int
862
    @param idx: the disk index
863
    @rtype: L{Disk}
864
    @return: the corresponding disk
865
    @raise errors.OpPrereqError: when the given index is not valid
866

867
    """
868
    try:
869
      idx = int(idx)
870
      return self.disks[idx]
871
    except (TypeError, ValueError), err:
872
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
873
                                 errors.ECODE_INVAL)
874
    except IndexError:
875
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
876
                                 " 0 to %d" % (idx, len(self.disks) - 1),
877
                                 errors.ECODE_INVAL)
878

    
879
  def ToDict(self):
880
    """Instance-specific conversion to standard python types.
881

882
    This replaces the children lists of objects with lists of standard
883
    python types.
884

885
    """
886
    bo = super(Instance, self).ToDict()
887

    
888
    for attr in "nics", "disks":
889
      alist = bo.get(attr, None)
890
      if alist:
891
        nlist = self._ContainerToDicts(alist)
892
      else:
893
        nlist = []
894
      bo[attr] = nlist
895
    return bo
896

    
897
  @classmethod
898
  def FromDict(cls, val):
899
    """Custom function for instances.
900

901
    """
902
    if "admin_state" not in val:
903
      if val.get("admin_up", False):
904
        val["admin_state"] = constants.ADMINST_UP
905
      else:
906
        val["admin_state"] = constants.ADMINST_DOWN
907
    if "admin_up" in val:
908
      del val["admin_up"]
909
    obj = super(Instance, cls).FromDict(val)
910
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
911
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
912
    return obj
913

    
914
  def UpgradeConfig(self):
915
    """Fill defaults for missing configuration values.
916

917
    """
918
    for nic in self.nics:
919
      nic.UpgradeConfig()
920
    for disk in self.disks:
921
      disk.UpgradeConfig()
922
    if self.hvparams:
923
      for key in constants.HVC_GLOBALS:
924
        try:
925
          del self.hvparams[key]
926
        except KeyError:
927
          pass
928
    if self.osparams is None:
929
      self.osparams = {}
930
    UpgradeBeParams(self.beparams)
931

    
932

    
933
class OS(ConfigObject):
934
  """Config object representing an operating system.
935

936
  @type supported_parameters: list
937
  @ivar supported_parameters: a list of tuples, name and description,
938
      containing the supported parameters by this OS
939

940
  @type VARIANT_DELIM: string
941
  @cvar VARIANT_DELIM: the variant delimiter
942

943
  """
944
  __slots__ = [
945
    "name",
946
    "path",
947
    "api_versions",
948
    "create_script",
949
    "export_script",
950
    "import_script",
951
    "rename_script",
952
    "verify_script",
953
    "supported_variants",
954
    "supported_parameters",
955
    ]
956

    
957
  VARIANT_DELIM = "+"
958

    
959
  @classmethod
960
  def SplitNameVariant(cls, name):
961
    """Splits the name into the proper name and variant.
962

963
    @param name: the OS (unprocessed) name
964
    @rtype: list
965
    @return: a list of two elements; if the original name didn't
966
        contain a variant, it's returned as an empty string
967

968
    """
969
    nv = name.split(cls.VARIANT_DELIM, 1)
970
    if len(nv) == 1:
971
      nv.append("")
972
    return nv
973

    
974
  @classmethod
975
  def GetName(cls, name):
976
    """Returns the proper name of the os (without the variant).
977

978
    @param name: the OS (unprocessed) name
979

980
    """
981
    return cls.SplitNameVariant(name)[0]
982

    
983
  @classmethod
984
  def GetVariant(cls, name):
985
    """Returns the variant the os (without the base name).
986

987
    @param name: the OS (unprocessed) name
988

989
    """
990
    return cls.SplitNameVariant(name)[1]
991

    
992

    
993
class Node(TaggableObject):
994
  """Config object representing a node."""
995
  __slots__ = [
996
    "name",
997
    "primary_ip",
998
    "secondary_ip",
999
    "serial_no",
1000
    "master_candidate",
1001
    "offline",
1002
    "drained",
1003
    "group",
1004
    "master_capable",
1005
    "vm_capable",
1006
    "ndparams",
1007
    "powered",
1008
    "hv_state",
1009
    "disk_state",
1010
    ] + _TIMESTAMPS + _UUID
1011

    
1012
  def UpgradeConfig(self):
1013
    """Fill defaults for missing configuration values.
1014

1015
    """
1016
    # pylint: disable=E0203
1017
    # because these are "defined" via slots, not manually
1018
    if self.master_capable is None:
1019
      self.master_capable = True
1020

    
1021
    if self.vm_capable is None:
1022
      self.vm_capable = True
1023

    
1024
    if self.ndparams is None:
1025
      self.ndparams = {}
1026

    
1027
    if self.powered is None:
1028
      self.powered = True
1029

    
1030

    
1031
class NodeGroup(TaggableObject):
1032
  """Config object representing a node group."""
1033
  __slots__ = [
1034
    "name",
1035
    "members",
1036
    "ndparams",
1037
    "serial_no",
1038
    "alloc_policy",
1039
    ] + _TIMESTAMPS + _UUID
1040

    
1041
  def ToDict(self):
1042
    """Custom function for nodegroup.
1043

1044
    This discards the members object, which gets recalculated and is only kept
1045
    in memory.
1046

1047
    """
1048
    mydict = super(NodeGroup, self).ToDict()
1049
    del mydict["members"]
1050
    return mydict
1051

    
1052
  @classmethod
1053
  def FromDict(cls, val):
1054
    """Custom function for nodegroup.
1055

1056
    The members slot is initialized to an empty list, upon deserialization.
1057

1058
    """
1059
    obj = super(NodeGroup, cls).FromDict(val)
1060
    obj.members = []
1061
    return obj
1062

    
1063
  def UpgradeConfig(self):
1064
    """Fill defaults for missing configuration values.
1065

1066
    """
1067
    if self.ndparams is None:
1068
      self.ndparams = {}
1069

    
1070
    if self.serial_no is None:
1071
      self.serial_no = 1
1072

    
1073
    if self.alloc_policy is None:
1074
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1075

    
1076
    # We only update mtime, and not ctime, since we would not be able to provide
1077
    # a correct value for creation time.
1078
    if self.mtime is None:
1079
      self.mtime = time.time()
1080

    
1081
  def FillND(self, node):
1082
    """Return filled out ndparams for L{objects.Node}
1083

1084
    @type node: L{objects.Node}
1085
    @param node: A Node object to fill
1086
    @return a copy of the node's ndparams with defaults filled
1087

1088
    """
1089
    return self.SimpleFillND(node.ndparams)
1090

    
1091
  def SimpleFillND(self, ndparams):
1092
    """Fill a given ndparams dict with defaults.
1093

1094
    @type ndparams: dict
1095
    @param ndparams: the dict to fill
1096
    @rtype: dict
1097
    @return: a copy of the passed in ndparams with missing keys filled
1098
        from the node group defaults
1099

1100
    """
1101
    return FillDict(self.ndparams, ndparams)
1102

    
1103

    
1104
class Cluster(TaggableObject):
1105
  """Config object representing the cluster."""
1106
  __slots__ = [
1107
    "serial_no",
1108
    "rsahostkeypub",
1109
    "highest_used_port",
1110
    "tcpudp_port_pool",
1111
    "mac_prefix",
1112
    "volume_group_name",
1113
    "reserved_lvs",
1114
    "drbd_usermode_helper",
1115
    "default_bridge",
1116
    "default_hypervisor",
1117
    "master_node",
1118
    "master_ip",
1119
    "master_netdev",
1120
    "master_netmask",
1121
    "use_external_mip_script",
1122
    "cluster_name",
1123
    "file_storage_dir",
1124
    "shared_file_storage_dir",
1125
    "enabled_hypervisors",
1126
    "hvparams",
1127
    "os_hvp",
1128
    "beparams",
1129
    "osparams",
1130
    "nicparams",
1131
    "ndparams",
1132
    "candidate_pool_size",
1133
    "modify_etc_hosts",
1134
    "modify_ssh_setup",
1135
    "maintain_node_health",
1136
    "uid_pool",
1137
    "default_iallocator",
1138
    "hidden_os",
1139
    "blacklisted_os",
1140
    "primary_ip_family",
1141
    "prealloc_wipe_disks",
1142
    ] + _TIMESTAMPS + _UUID
1143

    
1144
  def UpgradeConfig(self):
1145
    """Fill defaults for missing configuration values.
1146

1147
    """
1148
    # pylint: disable=E0203
1149
    # because these are "defined" via slots, not manually
1150
    if self.hvparams is None:
1151
      self.hvparams = constants.HVC_DEFAULTS
1152
    else:
1153
      for hypervisor in self.hvparams:
1154
        self.hvparams[hypervisor] = FillDict(
1155
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1156

    
1157
    if self.os_hvp is None:
1158
      self.os_hvp = {}
1159

    
1160
    # osparams added before 2.2
1161
    if self.osparams is None:
1162
      self.osparams = {}
1163

    
1164
    if self.ndparams is None:
1165
      self.ndparams = constants.NDC_DEFAULTS
1166

    
1167
    self.beparams = UpgradeGroupedParams(self.beparams,
1168
                                         constants.BEC_DEFAULTS)
1169
    for beparams_group in self.beparams:
1170
      UpgradeBeParams(self.beparams[beparams_group])
1171

    
1172
    migrate_default_bridge = not self.nicparams
1173
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1174
                                          constants.NICC_DEFAULTS)
1175
    if migrate_default_bridge:
1176
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1177
        self.default_bridge
1178

    
1179
    if self.modify_etc_hosts is None:
1180
      self.modify_etc_hosts = True
1181

    
1182
    if self.modify_ssh_setup is None:
1183
      self.modify_ssh_setup = True
1184

    
1185
    # default_bridge is no longer used in 2.1. The slot is left there to
1186
    # support auto-upgrading. It can be removed once we decide to deprecate
1187
    # upgrading straight from 2.0.
1188
    if self.default_bridge is not None:
1189
      self.default_bridge = None
1190

    
1191
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1192
    # code can be removed once upgrading straight from 2.0 is deprecated.
1193
    if self.default_hypervisor is not None:
1194
      self.enabled_hypervisors = ([self.default_hypervisor] +
1195
        [hvname for hvname in self.enabled_hypervisors
1196
         if hvname != self.default_hypervisor])
1197
      self.default_hypervisor = None
1198

    
1199
    # maintain_node_health added after 2.1.1
1200
    if self.maintain_node_health is None:
1201
      self.maintain_node_health = False
1202

    
1203
    if self.uid_pool is None:
1204
      self.uid_pool = []
1205

    
1206
    if self.default_iallocator is None:
1207
      self.default_iallocator = ""
1208

    
1209
    # reserved_lvs added before 2.2
1210
    if self.reserved_lvs is None:
1211
      self.reserved_lvs = []
1212

    
1213
    # hidden and blacklisted operating systems added before 2.2.1
1214
    if self.hidden_os is None:
1215
      self.hidden_os = []
1216

    
1217
    if self.blacklisted_os is None:
1218
      self.blacklisted_os = []
1219

    
1220
    # primary_ip_family added before 2.3
1221
    if self.primary_ip_family is None:
1222
      self.primary_ip_family = AF_INET
1223

    
1224
    if self.master_netmask is None:
1225
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1226
      self.master_netmask = ipcls.iplen
1227

    
1228
    if self.prealloc_wipe_disks is None:
1229
      self.prealloc_wipe_disks = False
1230

    
1231
    # shared_file_storage_dir added before 2.5
1232
    if self.shared_file_storage_dir is None:
1233
      self.shared_file_storage_dir = ""
1234

    
1235
    if self.use_external_mip_script is None:
1236
      self.use_external_mip_script = False
1237

    
1238
  def ToDict(self):
1239
    """Custom function for cluster.
1240

1241
    """
1242
    mydict = super(Cluster, self).ToDict()
1243
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1244
    return mydict
1245

    
1246
  @classmethod
1247
  def FromDict(cls, val):
1248
    """Custom function for cluster.
1249

1250
    """
1251
    obj = super(Cluster, cls).FromDict(val)
1252
    if not isinstance(obj.tcpudp_port_pool, set):
1253
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1254
    return obj
1255

    
1256
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1257
    """Get the default hypervisor parameters for the cluster.
1258

1259
    @param hypervisor: the hypervisor name
1260
    @param os_name: if specified, we'll also update the defaults for this OS
1261
    @param skip_keys: if passed, list of keys not to use
1262
    @return: the defaults dict
1263

1264
    """
1265
    if skip_keys is None:
1266
      skip_keys = []
1267

    
1268
    fill_stack = [self.hvparams.get(hypervisor, {})]
1269
    if os_name is not None:
1270
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1271
      fill_stack.append(os_hvp)
1272

    
1273
    ret_dict = {}
1274
    for o_dict in fill_stack:
1275
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1276

    
1277
    return ret_dict
1278

    
1279
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1280
    """Fill a given hvparams dict with cluster defaults.
1281

1282
    @type hv_name: string
1283
    @param hv_name: the hypervisor to use
1284
    @type os_name: string
1285
    @param os_name: the OS to use for overriding the hypervisor defaults
1286
    @type skip_globals: boolean
1287
    @param skip_globals: if True, the global hypervisor parameters will
1288
        not be filled
1289
    @rtype: dict
1290
    @return: a copy of the given hvparams with missing keys filled from
1291
        the cluster defaults
1292

1293
    """
1294
    if skip_globals:
1295
      skip_keys = constants.HVC_GLOBALS
1296
    else:
1297
      skip_keys = []
1298

    
1299
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1300
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1301

    
1302
  def FillHV(self, instance, skip_globals=False):
1303
    """Fill an instance's hvparams dict with cluster defaults.
1304

1305
    @type instance: L{objects.Instance}
1306
    @param instance: the instance parameter to fill
1307
    @type skip_globals: boolean
1308
    @param skip_globals: if True, the global hypervisor parameters will
1309
        not be filled
1310
    @rtype: dict
1311
    @return: a copy of the instance's hvparams with missing keys filled from
1312
        the cluster defaults
1313

1314
    """
1315
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1316
                             instance.hvparams, skip_globals)
1317

    
1318
  def SimpleFillBE(self, beparams):
1319
    """Fill a given beparams dict with cluster defaults.
1320

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

1327
    """
1328
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1329

    
1330
  def FillBE(self, instance):
1331
    """Fill an instance's beparams dict with cluster defaults.
1332

1333
    @type instance: L{objects.Instance}
1334
    @param instance: the instance parameter to fill
1335
    @rtype: dict
1336
    @return: a copy of the instance's beparams with missing keys filled from
1337
        the cluster defaults
1338

1339
    """
1340
    return self.SimpleFillBE(instance.beparams)
1341

    
1342
  def SimpleFillNIC(self, nicparams):
1343
    """Fill a given nicparams dict with cluster defaults.
1344

1345
    @type nicparams: dict
1346
    @param nicparams: the dict to fill
1347
    @rtype: dict
1348
    @return: a copy of the passed in nicparams with missing keys filled
1349
        from the cluster defaults
1350

1351
    """
1352
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1353

    
1354
  def SimpleFillOS(self, os_name, os_params):
1355
    """Fill an instance's osparams dict with cluster defaults.
1356

1357
    @type os_name: string
1358
    @param os_name: the OS name to use
1359
    @type os_params: dict
1360
    @param os_params: the dict to fill with default values
1361
    @rtype: dict
1362
    @return: a copy of the instance's osparams with missing keys filled from
1363
        the cluster defaults
1364

1365
    """
1366
    name_only = os_name.split("+", 1)[0]
1367
    # base OS
1368
    result = self.osparams.get(name_only, {})
1369
    # OS with variant
1370
    result = FillDict(result, self.osparams.get(os_name, {}))
1371
    # specified params
1372
    return FillDict(result, os_params)
1373

    
1374
  def FillND(self, node, nodegroup):
1375
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1376

1377
    @type node: L{objects.Node}
1378
    @param node: A Node object to fill
1379
    @type nodegroup: L{objects.NodeGroup}
1380
    @param nodegroup: A Node object to fill
1381
    @return a copy of the node's ndparams with defaults filled
1382

1383
    """
1384
    return self.SimpleFillND(nodegroup.FillND(node))
1385

    
1386
  def SimpleFillND(self, ndparams):
1387
    """Fill a given ndparams dict with defaults.
1388

1389
    @type ndparams: dict
1390
    @param ndparams: the dict to fill
1391
    @rtype: dict
1392
    @return: a copy of the passed in ndparams with missing keys filled
1393
        from the cluster defaults
1394

1395
    """
1396
    return FillDict(self.ndparams, ndparams)
1397

    
1398

    
1399
class BlockDevStatus(ConfigObject):
1400
  """Config object representing the status of a block device."""
1401
  __slots__ = [
1402
    "dev_path",
1403
    "major",
1404
    "minor",
1405
    "sync_percent",
1406
    "estimated_time",
1407
    "is_degraded",
1408
    "ldisk_status",
1409
    ]
1410

    
1411

    
1412
class ImportExportStatus(ConfigObject):
1413
  """Config object representing the status of an import or export."""
1414
  __slots__ = [
1415
    "recent_output",
1416
    "listen_port",
1417
    "connected",
1418
    "progress_mbytes",
1419
    "progress_throughput",
1420
    "progress_eta",
1421
    "progress_percent",
1422
    "exit_status",
1423
    "error_message",
1424
    ] + _TIMESTAMPS
1425

    
1426

    
1427
class ImportExportOptions(ConfigObject):
1428
  """Options for import/export daemon
1429

1430
  @ivar key_name: X509 key name (None for cluster certificate)
1431
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1432
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1433
  @ivar magic: Used to ensure the connection goes to the right disk
1434
  @ivar ipv6: Whether to use IPv6
1435
  @ivar connect_timeout: Number of seconds for establishing connection
1436

1437
  """
1438
  __slots__ = [
1439
    "key_name",
1440
    "ca_pem",
1441
    "compress",
1442
    "magic",
1443
    "ipv6",
1444
    "connect_timeout",
1445
    ]
1446

    
1447

    
1448
class ConfdRequest(ConfigObject):
1449
  """Object holding a confd request.
1450

1451
  @ivar protocol: confd protocol version
1452
  @ivar type: confd query type
1453
  @ivar query: query request
1454
  @ivar rsalt: requested reply salt
1455

1456
  """
1457
  __slots__ = [
1458
    "protocol",
1459
    "type",
1460
    "query",
1461
    "rsalt",
1462
    ]
1463

    
1464

    
1465
class ConfdReply(ConfigObject):
1466
  """Object holding a confd reply.
1467

1468
  @ivar protocol: confd protocol version
1469
  @ivar status: reply status code (ok, error)
1470
  @ivar answer: confd query reply
1471
  @ivar serial: configuration serial number
1472

1473
  """
1474
  __slots__ = [
1475
    "protocol",
1476
    "status",
1477
    "answer",
1478
    "serial",
1479
    ]
1480

    
1481

    
1482
class QueryFieldDefinition(ConfigObject):
1483
  """Object holding a query field definition.
1484

1485
  @ivar name: Field name
1486
  @ivar title: Human-readable title
1487
  @ivar kind: Field type
1488
  @ivar doc: Human-readable description
1489

1490
  """
1491
  __slots__ = [
1492
    "name",
1493
    "title",
1494
    "kind",
1495
    "doc",
1496
    ]
1497

    
1498

    
1499
class _QueryResponseBase(ConfigObject):
1500
  __slots__ = [
1501
    "fields",
1502
    ]
1503

    
1504
  def ToDict(self):
1505
    """Custom function for serializing.
1506

1507
    """
1508
    mydict = super(_QueryResponseBase, self).ToDict()
1509
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1510
    return mydict
1511

    
1512
  @classmethod
1513
  def FromDict(cls, val):
1514
    """Custom function for de-serializing.
1515

1516
    """
1517
    obj = super(_QueryResponseBase, cls).FromDict(val)
1518
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1519
    return obj
1520

    
1521

    
1522
class QueryRequest(ConfigObject):
1523
  """Object holding a query request.
1524

1525
  """
1526
  __slots__ = [
1527
    "what",
1528
    "fields",
1529
    "qfilter",
1530
    ]
1531

    
1532

    
1533
class QueryResponse(_QueryResponseBase):
1534
  """Object holding the response to a query.
1535

1536
  @ivar fields: List of L{QueryFieldDefinition} objects
1537
  @ivar data: Requested data
1538

1539
  """
1540
  __slots__ = [
1541
    "data",
1542
    ]
1543

    
1544

    
1545
class QueryFieldsRequest(ConfigObject):
1546
  """Object holding a request for querying available fields.
1547

1548
  """
1549
  __slots__ = [
1550
    "what",
1551
    "fields",
1552
    ]
1553

    
1554

    
1555
class QueryFieldsResponse(_QueryResponseBase):
1556
  """Object holding the response to a query for fields.
1557

1558
  @ivar fields: List of L{QueryFieldDefinition} objects
1559

1560
  """
1561
  __slots__ = [
1562
    ]
1563

    
1564

    
1565
class MigrationStatus(ConfigObject):
1566
  """Object holding the status of a migration.
1567

1568
  """
1569
  __slots__ = [
1570
    "status",
1571
    "transferred_ram",
1572
    "total_ram",
1573
    ]
1574

    
1575

    
1576
class InstanceConsole(ConfigObject):
1577
  """Object describing how to access the console of an instance.
1578

1579
  """
1580
  __slots__ = [
1581
    "instance",
1582
    "kind",
1583
    "message",
1584
    "host",
1585
    "port",
1586
    "user",
1587
    "command",
1588
    "display",
1589
    ]
1590

    
1591
  def Validate(self):
1592
    """Validates contents of this object.
1593

1594
    """
1595
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1596
    assert self.instance, "Missing instance name"
1597
    assert self.message or self.kind in [constants.CONS_SSH,
1598
                                         constants.CONS_SPICE,
1599
                                         constants.CONS_VNC]
1600
    assert self.host or self.kind == constants.CONS_MESSAGE
1601
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1602
                                      constants.CONS_SSH]
1603
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1604
                                      constants.CONS_SPICE,
1605
                                      constants.CONS_VNC]
1606
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1607
                                         constants.CONS_SPICE,
1608
                                         constants.CONS_VNC]
1609
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1610
                                         constants.CONS_SPICE,
1611
                                         constants.CONS_SSH]
1612
    return True
1613

    
1614

    
1615
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1616
  """Simple wrapper over ConfigParse that allows serialization.
1617

1618
  This class is basically ConfigParser.SafeConfigParser with two
1619
  additional methods that allow it to serialize/unserialize to/from a
1620
  buffer.
1621

1622
  """
1623
  def Dumps(self):
1624
    """Dump this instance and return the string representation."""
1625
    buf = StringIO()
1626
    self.write(buf)
1627
    return buf.getvalue()
1628

    
1629
  @classmethod
1630
  def Loads(cls, data):
1631
    """Load data from a string."""
1632
    buf = StringIO(data)
1633
    cfp = cls()
1634
    cfp.readfp(buf)
1635
    return cfp