Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 9ca8a7c5

History | View | Annotate | Download (45.1 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_state",
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
    if "admin_state" not in val:
888
      if val.get("admin_up", False):
889
        val["admin_state"] = constants.ADMINST_UP
890
      else:
891
        val["admin_state"] = constants.ADMINST_DOWN
892
    if "admin_up" in val:
893
      del val["admin_up"]
894
    obj = super(Instance, cls).FromDict(val)
895
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
896
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
897
    return obj
898

    
899
  def UpgradeConfig(self):
900
    """Fill defaults for missing configuration values.
901

902
    """
903
    for nic in self.nics:
904
      nic.UpgradeConfig()
905
    for disk in self.disks:
906
      disk.UpgradeConfig()
907
    if self.hvparams:
908
      for key in constants.HVC_GLOBALS:
909
        try:
910
          del self.hvparams[key]
911
        except KeyError:
912
          pass
913
    if self.osparams is None:
914
      self.osparams = {}
915

    
916

    
917
class OS(ConfigObject):
918
  """Config object representing an operating system.
919

920
  @type supported_parameters: list
921
  @ivar supported_parameters: a list of tuples, name and description,
922
      containing the supported parameters by this OS
923

924
  @type VARIANT_DELIM: string
925
  @cvar VARIANT_DELIM: the variant delimiter
926

927
  """
928
  __slots__ = [
929
    "name",
930
    "path",
931
    "api_versions",
932
    "create_script",
933
    "export_script",
934
    "import_script",
935
    "rename_script",
936
    "verify_script",
937
    "supported_variants",
938
    "supported_parameters",
939
    ]
940

    
941
  VARIANT_DELIM = "+"
942

    
943
  @classmethod
944
  def SplitNameVariant(cls, name):
945
    """Splits the name into the proper name and variant.
946

947
    @param name: the OS (unprocessed) name
948
    @rtype: list
949
    @return: a list of two elements; if the original name didn't
950
        contain a variant, it's returned as an empty string
951

952
    """
953
    nv = name.split(cls.VARIANT_DELIM, 1)
954
    if len(nv) == 1:
955
      nv.append("")
956
    return nv
957

    
958
  @classmethod
959
  def GetName(cls, name):
960
    """Returns the proper name of the os (without the variant).
961

962
    @param name: the OS (unprocessed) name
963

964
    """
965
    return cls.SplitNameVariant(name)[0]
966

    
967
  @classmethod
968
  def GetVariant(cls, name):
969
    """Returns the variant the os (without the base name).
970

971
    @param name: the OS (unprocessed) name
972

973
    """
974
    return cls.SplitNameVariant(name)[1]
975

    
976

    
977
class Node(TaggableObject):
978
  """Config object representing a node."""
979
  __slots__ = [
980
    "name",
981
    "primary_ip",
982
    "secondary_ip",
983
    "serial_no",
984
    "master_candidate",
985
    "offline",
986
    "drained",
987
    "group",
988
    "master_capable",
989
    "vm_capable",
990
    "ndparams",
991
    "powered",
992
    "hv_state",
993
    "disk_state",
994
    ] + _TIMESTAMPS + _UUID
995

    
996
  def UpgradeConfig(self):
997
    """Fill defaults for missing configuration values.
998

999
    """
1000
    # pylint: disable=E0203
1001
    # because these are "defined" via slots, not manually
1002
    if self.master_capable is None:
1003
      self.master_capable = True
1004

    
1005
    if self.vm_capable is None:
1006
      self.vm_capable = True
1007

    
1008
    if self.ndparams is None:
1009
      self.ndparams = {}
1010

    
1011
    if self.powered is None:
1012
      self.powered = True
1013

    
1014

    
1015
class NodeGroup(TaggableObject):
1016
  """Config object representing a node group."""
1017
  __slots__ = [
1018
    "name",
1019
    "members",
1020
    "ndparams",
1021
    "serial_no",
1022
    "alloc_policy",
1023
    ] + _TIMESTAMPS + _UUID
1024

    
1025
  def ToDict(self):
1026
    """Custom function for nodegroup.
1027

1028
    This discards the members object, which gets recalculated and is only kept
1029
    in memory.
1030

1031
    """
1032
    mydict = super(NodeGroup, self).ToDict()
1033
    del mydict["members"]
1034
    return mydict
1035

    
1036
  @classmethod
1037
  def FromDict(cls, val):
1038
    """Custom function for nodegroup.
1039

1040
    The members slot is initialized to an empty list, upon deserialization.
1041

1042
    """
1043
    obj = super(NodeGroup, cls).FromDict(val)
1044
    obj.members = []
1045
    return obj
1046

    
1047
  def UpgradeConfig(self):
1048
    """Fill defaults for missing configuration values.
1049

1050
    """
1051
    if self.ndparams is None:
1052
      self.ndparams = {}
1053

    
1054
    if self.serial_no is None:
1055
      self.serial_no = 1
1056

    
1057
    if self.alloc_policy is None:
1058
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1059

    
1060
    # We only update mtime, and not ctime, since we would not be able to provide
1061
    # a correct value for creation time.
1062
    if self.mtime is None:
1063
      self.mtime = time.time()
1064

    
1065
  def FillND(self, node):
1066
    """Return filled out ndparams for L{objects.Node}
1067

1068
    @type node: L{objects.Node}
1069
    @param node: A Node object to fill
1070
    @return a copy of the node's ndparams with defaults filled
1071

1072
    """
1073
    return self.SimpleFillND(node.ndparams)
1074

    
1075
  def SimpleFillND(self, ndparams):
1076
    """Fill a given ndparams dict with defaults.
1077

1078
    @type ndparams: dict
1079
    @param ndparams: the dict to fill
1080
    @rtype: dict
1081
    @return: a copy of the passed in ndparams with missing keys filled
1082
        from the node group defaults
1083

1084
    """
1085
    return FillDict(self.ndparams, ndparams)
1086

    
1087

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

    
1128
  def UpgradeConfig(self):
1129
    """Fill defaults for missing configuration values.
1130

1131
    """
1132
    # pylint: disable=E0203
1133
    # because these are "defined" via slots, not manually
1134
    if self.hvparams is None:
1135
      self.hvparams = constants.HVC_DEFAULTS
1136
    else:
1137
      for hypervisor in self.hvparams:
1138
        self.hvparams[hypervisor] = FillDict(
1139
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1140

    
1141
    if self.os_hvp is None:
1142
      self.os_hvp = {}
1143

    
1144
    # osparams added before 2.2
1145
    if self.osparams is None:
1146
      self.osparams = {}
1147

    
1148
    if self.ndparams is None:
1149
      self.ndparams = constants.NDC_DEFAULTS
1150

    
1151
    self.beparams = UpgradeGroupedParams(self.beparams,
1152
                                         constants.BEC_DEFAULTS)
1153
    migrate_default_bridge = not self.nicparams
1154
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1155
                                          constants.NICC_DEFAULTS)
1156
    if migrate_default_bridge:
1157
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1158
        self.default_bridge
1159

    
1160
    if self.modify_etc_hosts is None:
1161
      self.modify_etc_hosts = True
1162

    
1163
    if self.modify_ssh_setup is None:
1164
      self.modify_ssh_setup = True
1165

    
1166
    # default_bridge is no longer used in 2.1. The slot is left there to
1167
    # support auto-upgrading. It can be removed once we decide to deprecate
1168
    # upgrading straight from 2.0.
1169
    if self.default_bridge is not None:
1170
      self.default_bridge = None
1171

    
1172
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1173
    # code can be removed once upgrading straight from 2.0 is deprecated.
1174
    if self.default_hypervisor is not None:
1175
      self.enabled_hypervisors = ([self.default_hypervisor] +
1176
        [hvname for hvname in self.enabled_hypervisors
1177
         if hvname != self.default_hypervisor])
1178
      self.default_hypervisor = None
1179

    
1180
    # maintain_node_health added after 2.1.1
1181
    if self.maintain_node_health is None:
1182
      self.maintain_node_health = False
1183

    
1184
    if self.uid_pool is None:
1185
      self.uid_pool = []
1186

    
1187
    if self.default_iallocator is None:
1188
      self.default_iallocator = ""
1189

    
1190
    # reserved_lvs added before 2.2
1191
    if self.reserved_lvs is None:
1192
      self.reserved_lvs = []
1193

    
1194
    # hidden and blacklisted operating systems added before 2.2.1
1195
    if self.hidden_os is None:
1196
      self.hidden_os = []
1197

    
1198
    if self.blacklisted_os is None:
1199
      self.blacklisted_os = []
1200

    
1201
    # primary_ip_family added before 2.3
1202
    if self.primary_ip_family is None:
1203
      self.primary_ip_family = AF_INET
1204

    
1205
    if self.master_netmask is None:
1206
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1207
      self.master_netmask = ipcls.iplen
1208

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

    
1212
    # shared_file_storage_dir added before 2.5
1213
    if self.shared_file_storage_dir is None:
1214
      self.shared_file_storage_dir = ""
1215

    
1216
    if self.use_external_mip_script is None:
1217
      self.use_external_mip_script = False
1218

    
1219
  def ToDict(self):
1220
    """Custom function for cluster.
1221

1222
    """
1223
    mydict = super(Cluster, self).ToDict()
1224
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1225
    return mydict
1226

    
1227
  @classmethod
1228
  def FromDict(cls, val):
1229
    """Custom function for cluster.
1230

1231
    """
1232
    obj = super(Cluster, cls).FromDict(val)
1233
    if not isinstance(obj.tcpudp_port_pool, set):
1234
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1235
    return obj
1236

    
1237
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1238
    """Get the default hypervisor parameters for the cluster.
1239

1240
    @param hypervisor: the hypervisor name
1241
    @param os_name: if specified, we'll also update the defaults for this OS
1242
    @param skip_keys: if passed, list of keys not to use
1243
    @return: the defaults dict
1244

1245
    """
1246
    if skip_keys is None:
1247
      skip_keys = []
1248

    
1249
    fill_stack = [self.hvparams.get(hypervisor, {})]
1250
    if os_name is not None:
1251
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1252
      fill_stack.append(os_hvp)
1253

    
1254
    ret_dict = {}
1255
    for o_dict in fill_stack:
1256
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1257

    
1258
    return ret_dict
1259

    
1260
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1261
    """Fill a given hvparams dict with cluster defaults.
1262

1263
    @type hv_name: string
1264
    @param hv_name: the hypervisor to use
1265
    @type os_name: string
1266
    @param os_name: the OS to use for overriding the hypervisor defaults
1267
    @type skip_globals: boolean
1268
    @param skip_globals: if True, the global hypervisor parameters will
1269
        not be filled
1270
    @rtype: dict
1271
    @return: a copy of the given hvparams with missing keys filled from
1272
        the cluster defaults
1273

1274
    """
1275
    if skip_globals:
1276
      skip_keys = constants.HVC_GLOBALS
1277
    else:
1278
      skip_keys = []
1279

    
1280
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1281
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1282

    
1283
  def FillHV(self, instance, skip_globals=False):
1284
    """Fill an instance's hvparams dict with cluster defaults.
1285

1286
    @type instance: L{objects.Instance}
1287
    @param instance: the instance parameter to fill
1288
    @type skip_globals: boolean
1289
    @param skip_globals: if True, the global hypervisor parameters will
1290
        not be filled
1291
    @rtype: dict
1292
    @return: a copy of the instance's hvparams with missing keys filled from
1293
        the cluster defaults
1294

1295
    """
1296
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1297
                             instance.hvparams, skip_globals)
1298

    
1299
  def SimpleFillBE(self, beparams):
1300
    """Fill a given beparams dict with cluster defaults.
1301

1302
    @type beparams: dict
1303
    @param beparams: the dict to fill
1304
    @rtype: dict
1305
    @return: a copy of the passed in beparams with missing keys filled
1306
        from the cluster defaults
1307

1308
    """
1309
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1310

    
1311
  def FillBE(self, instance):
1312
    """Fill an instance's beparams dict with cluster defaults.
1313

1314
    @type instance: L{objects.Instance}
1315
    @param instance: the instance parameter to fill
1316
    @rtype: dict
1317
    @return: a copy of the instance's beparams with missing keys filled from
1318
        the cluster defaults
1319

1320
    """
1321
    return self.SimpleFillBE(instance.beparams)
1322

    
1323
  def SimpleFillNIC(self, nicparams):
1324
    """Fill a given nicparams dict with cluster defaults.
1325

1326
    @type nicparams: dict
1327
    @param nicparams: the dict to fill
1328
    @rtype: dict
1329
    @return: a copy of the passed in nicparams with missing keys filled
1330
        from the cluster defaults
1331

1332
    """
1333
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1334

    
1335
  def SimpleFillOS(self, os_name, os_params):
1336
    """Fill an instance's osparams dict with cluster defaults.
1337

1338
    @type os_name: string
1339
    @param os_name: the OS name to use
1340
    @type os_params: dict
1341
    @param os_params: the dict to fill with default values
1342
    @rtype: dict
1343
    @return: a copy of the instance's osparams with missing keys filled from
1344
        the cluster defaults
1345

1346
    """
1347
    name_only = os_name.split("+", 1)[0]
1348
    # base OS
1349
    result = self.osparams.get(name_only, {})
1350
    # OS with variant
1351
    result = FillDict(result, self.osparams.get(os_name, {}))
1352
    # specified params
1353
    return FillDict(result, os_params)
1354

    
1355
  def FillND(self, node, nodegroup):
1356
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1357

1358
    @type node: L{objects.Node}
1359
    @param node: A Node object to fill
1360
    @type nodegroup: L{objects.NodeGroup}
1361
    @param nodegroup: A Node object to fill
1362
    @return a copy of the node's ndparams with defaults filled
1363

1364
    """
1365
    return self.SimpleFillND(nodegroup.FillND(node))
1366

    
1367
  def SimpleFillND(self, ndparams):
1368
    """Fill a given ndparams dict with defaults.
1369

1370
    @type ndparams: dict
1371
    @param ndparams: the dict to fill
1372
    @rtype: dict
1373
    @return: a copy of the passed in ndparams with missing keys filled
1374
        from the cluster defaults
1375

1376
    """
1377
    return FillDict(self.ndparams, ndparams)
1378

    
1379

    
1380
class BlockDevStatus(ConfigObject):
1381
  """Config object representing the status of a block device."""
1382
  __slots__ = [
1383
    "dev_path",
1384
    "major",
1385
    "minor",
1386
    "sync_percent",
1387
    "estimated_time",
1388
    "is_degraded",
1389
    "ldisk_status",
1390
    ]
1391

    
1392

    
1393
class ImportExportStatus(ConfigObject):
1394
  """Config object representing the status of an import or export."""
1395
  __slots__ = [
1396
    "recent_output",
1397
    "listen_port",
1398
    "connected",
1399
    "progress_mbytes",
1400
    "progress_throughput",
1401
    "progress_eta",
1402
    "progress_percent",
1403
    "exit_status",
1404
    "error_message",
1405
    ] + _TIMESTAMPS
1406

    
1407

    
1408
class ImportExportOptions(ConfigObject):
1409
  """Options for import/export daemon
1410

1411
  @ivar key_name: X509 key name (None for cluster certificate)
1412
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1413
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1414
  @ivar magic: Used to ensure the connection goes to the right disk
1415
  @ivar ipv6: Whether to use IPv6
1416
  @ivar connect_timeout: Number of seconds for establishing connection
1417

1418
  """
1419
  __slots__ = [
1420
    "key_name",
1421
    "ca_pem",
1422
    "compress",
1423
    "magic",
1424
    "ipv6",
1425
    "connect_timeout",
1426
    ]
1427

    
1428

    
1429
class ConfdRequest(ConfigObject):
1430
  """Object holding a confd request.
1431

1432
  @ivar protocol: confd protocol version
1433
  @ivar type: confd query type
1434
  @ivar query: query request
1435
  @ivar rsalt: requested reply salt
1436

1437
  """
1438
  __slots__ = [
1439
    "protocol",
1440
    "type",
1441
    "query",
1442
    "rsalt",
1443
    ]
1444

    
1445

    
1446
class ConfdReply(ConfigObject):
1447
  """Object holding a confd reply.
1448

1449
  @ivar protocol: confd protocol version
1450
  @ivar status: reply status code (ok, error)
1451
  @ivar answer: confd query reply
1452
  @ivar serial: configuration serial number
1453

1454
  """
1455
  __slots__ = [
1456
    "protocol",
1457
    "status",
1458
    "answer",
1459
    "serial",
1460
    ]
1461

    
1462

    
1463
class QueryFieldDefinition(ConfigObject):
1464
  """Object holding a query field definition.
1465

1466
  @ivar name: Field name
1467
  @ivar title: Human-readable title
1468
  @ivar kind: Field type
1469
  @ivar doc: Human-readable description
1470

1471
  """
1472
  __slots__ = [
1473
    "name",
1474
    "title",
1475
    "kind",
1476
    "doc",
1477
    ]
1478

    
1479

    
1480
class _QueryResponseBase(ConfigObject):
1481
  __slots__ = [
1482
    "fields",
1483
    ]
1484

    
1485
  def ToDict(self):
1486
    """Custom function for serializing.
1487

1488
    """
1489
    mydict = super(_QueryResponseBase, self).ToDict()
1490
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1491
    return mydict
1492

    
1493
  @classmethod
1494
  def FromDict(cls, val):
1495
    """Custom function for de-serializing.
1496

1497
    """
1498
    obj = super(_QueryResponseBase, cls).FromDict(val)
1499
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1500
    return obj
1501

    
1502

    
1503
class QueryRequest(ConfigObject):
1504
  """Object holding a query request.
1505

1506
  """
1507
  __slots__ = [
1508
    "what",
1509
    "fields",
1510
    "qfilter",
1511
    ]
1512

    
1513

    
1514
class QueryResponse(_QueryResponseBase):
1515
  """Object holding the response to a query.
1516

1517
  @ivar fields: List of L{QueryFieldDefinition} objects
1518
  @ivar data: Requested data
1519

1520
  """
1521
  __slots__ = [
1522
    "data",
1523
    ]
1524

    
1525

    
1526
class QueryFieldsRequest(ConfigObject):
1527
  """Object holding a request for querying available fields.
1528

1529
  """
1530
  __slots__ = [
1531
    "what",
1532
    "fields",
1533
    ]
1534

    
1535

    
1536
class QueryFieldsResponse(_QueryResponseBase):
1537
  """Object holding the response to a query for fields.
1538

1539
  @ivar fields: List of L{QueryFieldDefinition} objects
1540

1541
  """
1542
  __slots__ = [
1543
    ]
1544

    
1545

    
1546
class MigrationStatus(ConfigObject):
1547
  """Object holding the status of a migration.
1548

1549
  """
1550
  __slots__ = [
1551
    "status",
1552
    "transferred_ram",
1553
    "total_ram",
1554
    ]
1555

    
1556

    
1557
class InstanceConsole(ConfigObject):
1558
  """Object describing how to access the console of an instance.
1559

1560
  """
1561
  __slots__ = [
1562
    "instance",
1563
    "kind",
1564
    "message",
1565
    "host",
1566
    "port",
1567
    "user",
1568
    "command",
1569
    "display",
1570
    ]
1571

    
1572
  def Validate(self):
1573
    """Validates contents of this object.
1574

1575
    """
1576
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1577
    assert self.instance, "Missing instance name"
1578
    assert self.message or self.kind in [constants.CONS_SSH,
1579
                                         constants.CONS_SPICE,
1580
                                         constants.CONS_VNC]
1581
    assert self.host or self.kind == constants.CONS_MESSAGE
1582
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1583
                                      constants.CONS_SSH]
1584
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1585
                                      constants.CONS_SPICE,
1586
                                      constants.CONS_VNC]
1587
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1588
                                         constants.CONS_SPICE,
1589
                                         constants.CONS_VNC]
1590
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1591
                                         constants.CONS_SPICE,
1592
                                         constants.CONS_SSH]
1593
    return True
1594

    
1595

    
1596
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1597
  """Simple wrapper over ConfigParse that allows serialization.
1598

1599
  This class is basically ConfigParser.SafeConfigParser with two
1600
  additional methods that allow it to serialize/unserialize to/from a
1601
  buffer.
1602

1603
  """
1604
  def Dumps(self):
1605
    """Dump this instance and return the string representation."""
1606
    buf = StringIO()
1607
    self.write(buf)
1608
    return buf.getvalue()
1609

    
1610
  @classmethod
1611
  def Loads(cls, data):
1612
    """Load data from a string."""
1613
    buf = StringIO(data)
1614
    cfp = cls()
1615
    cfp.readfp(buf)
1616
    return cfp