Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 1ae17369

History | View | Annotate | Download (43.3 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-msg=E0203,W0201
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
import ConfigParser
37
import re
38
import copy
39
import time
40
from cStringIO import StringIO
41

    
42
from ganeti import errors
43
from ganeti import constants
44

    
45
from socket import AF_INET
46

    
47

    
48
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
49
           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
50

    
51
_TIMESTAMPS = ["ctime", "mtime"]
52
_UUID = ["uuid"]
53

    
54

    
55
def FillDict(defaults_dict, custom_dict, skip_keys=None):
56
  """Basic function to apply settings on top a default dict.
57

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

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

    
78

    
79
def UpgradeGroupedParams(target, defaults):
80
  """Update all groups for the target parameter.
81

82
  @type target: dict of dicts
83
  @param target: {group: {parameter: value}}
84
  @type defaults: dict
85
  @param defaults: default parameter values
86

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

    
95

    
96
class ConfigObject(object):
97
  """A generic config object.
98

99
  It has the following properties:
100

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

105
  Classes derived from this must always declare __slots__ (we use many
106
  config objects and the memory reduction is useful)
107

108
  """
109
  __slots__ = []
110

    
111
  def __init__(self, **kwargs):
112
    for k, v in kwargs.iteritems():
113
      setattr(self, k, v)
114

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

    
121
  def __setstate__(self, state):
122
    slots = self._all_slots()
123
    for name in state:
124
      if name in slots:
125
        setattr(self, name, state[name])
126

    
127
  @classmethod
128
  def _all_slots(cls):
129
    """Compute the list of all declared slots for a class.
130

131
    """
132
    slots = []
133
    for parent in cls.__mro__:
134
      slots.extend(getattr(parent, "__slots__", []))
135
    return slots
136

    
137
  def ToDict(self):
138
    """Convert to a dict holding only standard python types.
139

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

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

    
154
  __getstate__ = ToDict
155

    
156
  @classmethod
157
  def FromDict(cls, val):
158
    """Create an object from a dictionary.
159

160
    This generic routine takes a dict, instantiates a new instance of
161
    the given class, and sets attributes based on the dict content.
162

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

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

    
176
  @staticmethod
177
  def _ContainerToDicts(container):
178
    """Convert the elements of a container to standard python types.
179

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

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

    
194
  @staticmethod
195
  def _ContainerFromDicts(source, c_type, e_type):
196
    """Convert a container from standard python types.
197

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

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

    
217
  def Copy(self):
218
    """Makes a deep copy of the current object and its children.
219

220
    """
221
    dict_form = self.ToDict()
222
    clone_obj = self.__class__.FromDict(dict_form)
223
    return clone_obj
224

    
225
  def __repr__(self):
226
    """Implement __repr__ for ConfigObjects."""
227
    return repr(self.ToDict())
228

    
229
  def UpgradeConfig(self):
230
    """Fill defaults for missing configuration values.
231

232
    This method will be called at configuration load time, and its
233
    implementation will be object dependent.
234

235
    """
236
    pass
237

    
238

    
239
class TaggableObject(ConfigObject):
240
  """An generic class supporting tags.
241

242
  """
243
  __slots__ = ["tags"]
244
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
245

    
246
  @classmethod
247
  def ValidateTag(cls, tag):
248
    """Check if a tag is valid.
249

250
    If the tag is invalid, an errors.TagError will be raised. The
251
    function has no return value.
252

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

    
264
  def GetTags(self):
265
    """Return the tags list.
266

267
    """
268
    tags = getattr(self, "tags", None)
269
    if tags is None:
270
      tags = self.tags = set()
271
    return tags
272

    
273
  def AddTag(self, tag):
274
    """Add a new tag.
275

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

    
283
  def RemoveTag(self, tag):
284
    """Remove a tag.
285

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

    
294
  def ToDict(self):
295
    """Taggable-object-specific conversion to standard python types.
296

297
    This replaces the tags set with a list.
298

299
    """
300
    bo = super(TaggableObject, self).ToDict()
301

    
302
    tags = bo.get("tags", None)
303
    if isinstance(tags, set):
304
      bo["tags"] = list(tags)
305
    return bo
306

    
307
  @classmethod
308
  def FromDict(cls, val):
309
    """Custom function for instances.
310

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

    
317

    
318
class ConfigData(ConfigObject):
319
  """Top-level config object."""
320
  __slots__ = [
321
    "version",
322
    "cluster",
323
    "nodes",
324
    "nodegroups",
325
    "instances",
326
    "serial_no",
327
    ] + _TIMESTAMPS
328

    
329
  def ToDict(self):
330
    """Custom function for top-level config data.
331

332
    This just replaces the list of instances, nodes and the cluster
333
    with standard python types.
334

335
    """
336
    mydict = super(ConfigData, self).ToDict()
337
    mydict["cluster"] = mydict["cluster"].ToDict()
338
    for key in "nodes", "instances", "nodegroups":
339
      mydict[key] = self._ContainerToDicts(mydict[key])
340

    
341
    return mydict
342

    
343
  @classmethod
344
  def FromDict(cls, val):
345
    """Custom function for top-level config data
346

347
    """
348
    obj = super(ConfigData, cls).FromDict(val)
349
    obj.cluster = Cluster.FromDict(obj.cluster)
350
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
351
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
352
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
353
    return obj
354

    
355
  def HasAnyDiskOfType(self, dev_type):
356
    """Check if in there is at disk of the given type in the configuration.
357

358
    @type dev_type: L{constants.LDS_BLOCK}
359
    @param dev_type: the type to look for
360
    @rtype: boolean
361
    @return: boolean indicating if a disk of the given type was found or not
362

363
    """
364
    for instance in self.instances.values():
365
      for disk in instance.disks:
366
        if disk.IsBasedOnDiskType(dev_type):
367
          return True
368
    return False
369

    
370
  def UpgradeConfig(self):
371
    """Fill defaults for missing configuration values.
372

373
    """
374
    self.cluster.UpgradeConfig()
375
    for node in self.nodes.values():
376
      node.UpgradeConfig()
377
    for instance in self.instances.values():
378
      instance.UpgradeConfig()
379
    if self.nodegroups is None:
380
      self.nodegroups = {}
381
    for nodegroup in self.nodegroups.values():
382
      nodegroup.UpgradeConfig()
383
    if self.cluster.drbd_usermode_helper is None:
384
      # To decide if we set an helper let's check if at least one instance has
385
      # a DRBD disk. This does not cover all the possible scenarios but it
386
      # gives a good approximation.
387
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
388
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
389

    
390

    
391
class NIC(ConfigObject):
392
  """Config object representing a network card."""
393
  __slots__ = ["mac", "ip", "nicparams"]
394

    
395
  @classmethod
396
  def CheckParameterSyntax(cls, nicparams):
397
    """Check the given parameters for validity.
398

399
    @type nicparams:  dict
400
    @param nicparams: dictionary with parameter names/value
401
    @raise errors.ConfigurationError: when a parameter is not valid
402

403
    """
404
    if nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES:
405
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
406
      raise errors.ConfigurationError(err)
407

    
408
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
409
        not nicparams[constants.NIC_LINK]):
410
      err = "Missing bridged nic link"
411
      raise errors.ConfigurationError(err)
412

    
413

    
414
class Disk(ConfigObject):
415
  """Config object representing a block device."""
416
  __slots__ = ["dev_type", "logical_id", "physical_id",
417
               "children", "iv_name", "size", "mode"]
418

    
419
  def CreateOnSecondary(self):
420
    """Test if this device needs to be created on a secondary node."""
421
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
422

    
423
  def AssembleOnSecondary(self):
424
    """Test if this device needs to be assembled on a secondary node."""
425
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
426

    
427
  def OpenOnSecondary(self):
428
    """Test if this device needs to be opened on a secondary node."""
429
    return self.dev_type in (constants.LD_LV,)
430

    
431
  def StaticDevPath(self):
432
    """Return the device path if this device type has a static one.
433

434
    Some devices (LVM for example) live always at the same /dev/ path,
435
    irrespective of their status. For such devices, we return this
436
    path, for others we return None.
437

438
    @warning: The path returned is not a normalized pathname; callers
439
        should check that it is a valid path.
440

441
    """
442
    if self.dev_type == constants.LD_LV:
443
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
444
    return None
445

    
446
  def ChildrenNeeded(self):
447
    """Compute the needed number of children for activation.
448

449
    This method will return either -1 (all children) or a positive
450
    number denoting the minimum number of children needed for
451
    activation (only mirrored devices will usually return >=0).
452

453
    Currently, only DRBD8 supports diskless activation (therefore we
454
    return 0), for all other we keep the previous semantics and return
455
    -1.
456

457
    """
458
    if self.dev_type == constants.LD_DRBD8:
459
      return 0
460
    return -1
461

    
462
  def IsBasedOnDiskType(self, dev_type):
463
    """Check if the disk or its children are based on the given type.
464

465
    @type dev_type: L{constants.LDS_BLOCK}
466
    @param dev_type: the type to look for
467
    @rtype: boolean
468
    @return: boolean indicating if a device of the given type was found or not
469

470
    """
471
    if self.children:
472
      for child in self.children:
473
        if child.IsBasedOnDiskType(dev_type):
474
          return True
475
    return self.dev_type == dev_type
476

    
477
  def GetNodes(self, node):
478
    """This function returns the nodes this device lives on.
479

480
    Given the node on which the parent of the device lives on (or, in
481
    case of a top-level device, the primary node of the devices'
482
    instance), this function will return a list of nodes on which this
483
    devices needs to (or can) be assembled.
484

485
    """
486
    if self.dev_type in [constants.LD_LV, constants.LD_FILE]:
487
      result = [node]
488
    elif self.dev_type in constants.LDS_DRBD:
489
      result = [self.logical_id[0], self.logical_id[1]]
490
      if node not in result:
491
        raise errors.ConfigurationError("DRBD device passed unknown node")
492
    else:
493
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
494
    return result
495

    
496
  def ComputeNodeTree(self, parent_node):
497
    """Compute the node/disk tree for this disk and its children.
498

499
    This method, given the node on which the parent disk lives, will
500
    return the list of all (node, disk) pairs which describe the disk
501
    tree in the most compact way. For example, a drbd/lvm stack
502
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
503
    which represents all the top-level devices on the nodes.
504

505
    """
506
    my_nodes = self.GetNodes(parent_node)
507
    result = [(node, self) for node in my_nodes]
508
    if not self.children:
509
      # leaf device
510
      return result
511
    for node in my_nodes:
512
      for child in self.children:
513
        child_result = child.ComputeNodeTree(node)
514
        if len(child_result) == 1:
515
          # child (and all its descendants) is simple, doesn't split
516
          # over multiple hosts, so we don't need to describe it, our
517
          # own entry for this node describes it completely
518
          continue
519
        else:
520
          # check if child nodes differ from my nodes; note that
521
          # subdisk can differ from the child itself, and be instead
522
          # one of its descendants
523
          for subnode, subdisk in child_result:
524
            if subnode not in my_nodes:
525
              result.append((subnode, subdisk))
526
            # otherwise child is under our own node, so we ignore this
527
            # entry (but probably the other results in the list will
528
            # be different)
529
    return result
530

    
531
  def ComputeGrowth(self, amount):
532
    """Compute the per-VG growth requirements.
533

534
    This only works for VG-based disks.
535

536
    @type amount: integer
537
    @param amount: the desired increase in (user-visible) disk space
538
    @rtype: dict
539
    @return: a dictionary of volume-groups and the required size
540

541
    """
542
    if self.dev_type == constants.LD_LV:
543
      return {self.logical_id[0]: amount}
544
    elif self.dev_type == constants.LD_DRBD8:
545
      if self.children:
546
        return self.children[0].ComputeGrowth(amount)
547
      else:
548
        return {}
549
    else:
550
      # Other disk types do not require VG space
551
      return {}
552

    
553
  def RecordGrow(self, amount):
554
    """Update the size of this disk after growth.
555

556
    This method recurses over the disks's children and updates their
557
    size correspondigly. The method needs to be kept in sync with the
558
    actual algorithms from bdev.
559

560
    """
561
    if self.dev_type == constants.LD_LV or self.dev_type == constants.LD_FILE:
562
      self.size += amount
563
    elif self.dev_type == constants.LD_DRBD8:
564
      if self.children:
565
        self.children[0].RecordGrow(amount)
566
      self.size += amount
567
    else:
568
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
569
                                   " disk type %s" % self.dev_type)
570

    
571
  def UnsetSize(self):
572
    """Sets recursively the size to zero for the disk and its children.
573

574
    """
575
    if self.children:
576
      for child in self.children:
577
        child.UnsetSize()
578
    self.size = 0
579

    
580
  def SetPhysicalID(self, target_node, nodes_ip):
581
    """Convert the logical ID to the physical ID.
582

583
    This is used only for drbd, which needs ip/port configuration.
584

585
    The routine descends down and updates its children also, because
586
    this helps when the only the top device is passed to the remote
587
    node.
588

589
    Arguments:
590
      - target_node: the node we wish to configure for
591
      - nodes_ip: a mapping of node name to ip
592

593
    The target_node must exist in in nodes_ip, and must be one of the
594
    nodes in the logical ID for each of the DRBD devices encountered
595
    in the disk tree.
596

597
    """
598
    if self.children:
599
      for child in self.children:
600
        child.SetPhysicalID(target_node, nodes_ip)
601

    
602
    if self.logical_id is None and self.physical_id is not None:
603
      return
604
    if self.dev_type in constants.LDS_DRBD:
605
      pnode, snode, port, pminor, sminor, secret = self.logical_id
606
      if target_node not in (pnode, snode):
607
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
608
                                        target_node)
609
      pnode_ip = nodes_ip.get(pnode, None)
610
      snode_ip = nodes_ip.get(snode, None)
611
      if pnode_ip is None or snode_ip is None:
612
        raise errors.ConfigurationError("Can't find primary or secondary node"
613
                                        " for %s" % str(self))
614
      p_data = (pnode_ip, port)
615
      s_data = (snode_ip, port)
616
      if pnode == target_node:
617
        self.physical_id = p_data + s_data + (pminor, secret)
618
      else: # it must be secondary, we tested above
619
        self.physical_id = s_data + p_data + (sminor, secret)
620
    else:
621
      self.physical_id = self.logical_id
622
    return
623

    
624
  def ToDict(self):
625
    """Disk-specific conversion to standard python types.
626

627
    This replaces the children lists of objects with lists of
628
    standard python types.
629

630
    """
631
    bo = super(Disk, self).ToDict()
632

    
633
    for attr in ("children",):
634
      alist = bo.get(attr, None)
635
      if alist:
636
        bo[attr] = self._ContainerToDicts(alist)
637
    return bo
638

    
639
  @classmethod
640
  def FromDict(cls, val):
641
    """Custom function for Disks
642

643
    """
644
    obj = super(Disk, cls).FromDict(val)
645
    if obj.children:
646
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
647
    if obj.logical_id and isinstance(obj.logical_id, list):
648
      obj.logical_id = tuple(obj.logical_id)
649
    if obj.physical_id and isinstance(obj.physical_id, list):
650
      obj.physical_id = tuple(obj.physical_id)
651
    if obj.dev_type in constants.LDS_DRBD:
652
      # we need a tuple of length six here
653
      if len(obj.logical_id) < 6:
654
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
655
    return obj
656

    
657
  def __str__(self):
658
    """Custom str() formatter for disks.
659

660
    """
661
    if self.dev_type == constants.LD_LV:
662
      val =  "<LogicalVolume(/dev/%s/%s" % self.logical_id
663
    elif self.dev_type in constants.LDS_DRBD:
664
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
665
      val = "<DRBD8("
666
      if self.physical_id is None:
667
        phy = "unconfigured"
668
      else:
669
        phy = ("configured as %s:%s %s:%s" %
670
               (self.physical_id[0], self.physical_id[1],
671
                self.physical_id[2], self.physical_id[3]))
672

    
673
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
674
              (node_a, minor_a, node_b, minor_b, port, phy))
675
      if self.children and self.children.count(None) == 0:
676
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
677
      else:
678
        val += "no local storage"
679
    else:
680
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
681
             (self.dev_type, self.logical_id, self.physical_id, self.children))
682
    if self.iv_name is None:
683
      val += ", not visible"
684
    else:
685
      val += ", visible as /dev/%s" % self.iv_name
686
    if isinstance(self.size, int):
687
      val += ", size=%dm)>" % self.size
688
    else:
689
      val += ", size='%s')>" % (self.size,)
690
    return val
691

    
692
  def Verify(self):
693
    """Checks that this disk is correctly configured.
694

695
    """
696
    all_errors = []
697
    if self.mode not in constants.DISK_ACCESS_SET:
698
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
699
    return all_errors
700

    
701
  def UpgradeConfig(self):
702
    """Fill defaults for missing configuration values.
703

704
    """
705
    if self.children:
706
      for child in self.children:
707
        child.UpgradeConfig()
708
    # add here config upgrade for this disk
709

    
710

    
711
class Instance(TaggableObject):
712
  """Config object representing an instance."""
713
  __slots__ = [
714
    "name",
715
    "primary_node",
716
    "os",
717
    "hypervisor",
718
    "hvparams",
719
    "beparams",
720
    "osparams",
721
    "admin_up",
722
    "nics",
723
    "disks",
724
    "disk_template",
725
    "network_port",
726
    "serial_no",
727
    ] + _TIMESTAMPS + _UUID
728

    
729
  def _ComputeSecondaryNodes(self):
730
    """Compute the list of secondary nodes.
731

732
    This is a simple wrapper over _ComputeAllNodes.
733

734
    """
735
    all_nodes = set(self._ComputeAllNodes())
736
    all_nodes.discard(self.primary_node)
737
    return tuple(all_nodes)
738

    
739
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
740
                             "List of secondary nodes")
741

    
742
  def _ComputeAllNodes(self):
743
    """Compute the list of all nodes.
744

745
    Since the data is already there (in the drbd disks), keeping it as
746
    a separate normal attribute is redundant and if not properly
747
    synchronised can cause problems. Thus it's better to compute it
748
    dynamically.
749

750
    """
751
    def _Helper(nodes, device):
752
      """Recursively computes nodes given a top device."""
753
      if device.dev_type in constants.LDS_DRBD:
754
        nodea, nodeb = device.logical_id[:2]
755
        nodes.add(nodea)
756
        nodes.add(nodeb)
757
      if device.children:
758
        for child in device.children:
759
          _Helper(nodes, child)
760

    
761
    all_nodes = set()
762
    all_nodes.add(self.primary_node)
763
    for device in self.disks:
764
      _Helper(all_nodes, device)
765
    return tuple(all_nodes)
766

    
767
  all_nodes = property(_ComputeAllNodes, None, None,
768
                       "List of all nodes of the instance")
769

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

773
    This function figures out what logical volumes should belong on
774
    which nodes, recursing through a device tree.
775

776
    @param lvmap: optional dictionary to receive the
777
        'node' : ['lv', ...] data.
778

779
    @return: None if lvmap arg is given, otherwise, a dictionary of
780
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
781
        volumeN is of the form "vg_name/lv_name", compatible with
782
        GetVolumeList()
783

784
    """
785
    if node == None:
786
      node = self.primary_node
787

    
788
    if lvmap is None:
789
      lvmap = { node : [] }
790
      ret = lvmap
791
    else:
792
      if not node in lvmap:
793
        lvmap[node] = []
794
      ret = None
795

    
796
    if not devs:
797
      devs = self.disks
798

    
799
    for dev in devs:
800
      if dev.dev_type == constants.LD_LV:
801
        lvmap[node].append(dev.logical_id[0]+"/"+dev.logical_id[1])
802

    
803
      elif dev.dev_type in constants.LDS_DRBD:
804
        if dev.children:
805
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
806
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
807

    
808
      elif dev.children:
809
        self.MapLVsByNode(lvmap, dev.children, node)
810

    
811
    return ret
812

    
813
  def FindDisk(self, idx):
814
    """Find a disk given having a specified index.
815

816
    This is just a wrapper that does validation of the index.
817

818
    @type idx: int
819
    @param idx: the disk index
820
    @rtype: L{Disk}
821
    @return: the corresponding disk
822
    @raise errors.OpPrereqError: when the given index is not valid
823

824
    """
825
    try:
826
      idx = int(idx)
827
      return self.disks[idx]
828
    except (TypeError, ValueError), err:
829
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
830
                                 errors.ECODE_INVAL)
831
    except IndexError:
832
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
833
                                 " 0 to %d" % (idx, len(self.disks) - 1),
834
                                 errors.ECODE_INVAL)
835

    
836
  def ToDict(self):
837
    """Instance-specific conversion to standard python types.
838

839
    This replaces the children lists of objects with lists of standard
840
    python types.
841

842
    """
843
    bo = super(Instance, self).ToDict()
844

    
845
    for attr in "nics", "disks":
846
      alist = bo.get(attr, None)
847
      if alist:
848
        nlist = self._ContainerToDicts(alist)
849
      else:
850
        nlist = []
851
      bo[attr] = nlist
852
    return bo
853

    
854
  @classmethod
855
  def FromDict(cls, val):
856
    """Custom function for instances.
857

858
    """
859
    obj = super(Instance, cls).FromDict(val)
860
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
861
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
862
    return obj
863

    
864
  def UpgradeConfig(self):
865
    """Fill defaults for missing configuration values.
866

867
    """
868
    for nic in self.nics:
869
      nic.UpgradeConfig()
870
    for disk in self.disks:
871
      disk.UpgradeConfig()
872
    if self.hvparams:
873
      for key in constants.HVC_GLOBALS:
874
        try:
875
          del self.hvparams[key]
876
        except KeyError:
877
          pass
878
    if self.osparams is None:
879
      self.osparams = {}
880

    
881

    
882
class OS(ConfigObject):
883
  """Config object representing an operating system.
884

885
  @type supported_parameters: list
886
  @ivar supported_parameters: a list of tuples, name and description,
887
      containing the supported parameters by this OS
888

889
  @type VARIANT_DELIM: string
890
  @cvar VARIANT_DELIM: the variant delimiter
891

892
  """
893
  __slots__ = [
894
    "name",
895
    "path",
896
    "api_versions",
897
    "create_script",
898
    "export_script",
899
    "import_script",
900
    "rename_script",
901
    "verify_script",
902
    "supported_variants",
903
    "supported_parameters",
904
    ]
905

    
906
  VARIANT_DELIM = "+"
907

    
908
  @classmethod
909
  def SplitNameVariant(cls, name):
910
    """Splits the name into the proper name and variant.
911

912
    @param name: the OS (unprocessed) name
913
    @rtype: list
914
    @return: a list of two elements; if the original name didn't
915
        contain a variant, it's returned as an empty string
916

917
    """
918
    nv = name.split(cls.VARIANT_DELIM, 1)
919
    if len(nv) == 1:
920
      nv.append("")
921
    return nv
922

    
923
  @classmethod
924
  def GetName(cls, name):
925
    """Returns the proper name of the os (without the variant).
926

927
    @param name: the OS (unprocessed) name
928

929
    """
930
    return cls.SplitNameVariant(name)[0]
931

    
932
  @classmethod
933
  def GetVariant(cls, name):
934
    """Returns the variant the os (without the base name).
935

936
    @param name: the OS (unprocessed) name
937

938
    """
939
    return cls.SplitNameVariant(name)[1]
940

    
941

    
942
class Node(TaggableObject):
943
  """Config object representing a node."""
944
  __slots__ = [
945
    "name",
946
    "primary_ip",
947
    "secondary_ip",
948
    "serial_no",
949
    "master_candidate",
950
    "offline",
951
    "drained",
952
    "group",
953
    "master_capable",
954
    "vm_capable",
955
    "ndparams",
956
    "powered",
957
    ] + _TIMESTAMPS + _UUID
958

    
959
  def UpgradeConfig(self):
960
    """Fill defaults for missing configuration values.
961

962
    """
963
    # pylint: disable-msg=E0203
964
    # because these are "defined" via slots, not manually
965
    if self.master_capable is None:
966
      self.master_capable = True
967

    
968
    if self.vm_capable is None:
969
      self.vm_capable = True
970

    
971
    if self.ndparams is None:
972
      self.ndparams = {}
973

    
974
    if self.powered is None:
975
      self.powered = True
976

    
977

    
978
class NodeGroup(ConfigObject):
979
  """Config object representing a node group."""
980
  __slots__ = [
981
    "name",
982
    "members",
983
    "ndparams",
984
    "serial_no",
985
    "alloc_policy",
986
    ] + _TIMESTAMPS + _UUID
987

    
988
  def ToDict(self):
989
    """Custom function for nodegroup.
990

991
    This discards the members object, which gets recalculated and is only kept
992
    in memory.
993

994
    """
995
    mydict = super(NodeGroup, self).ToDict()
996
    del mydict["members"]
997
    return mydict
998

    
999
  @classmethod
1000
  def FromDict(cls, val):
1001
    """Custom function for nodegroup.
1002

1003
    The members slot is initialized to an empty list, upon deserialization.
1004

1005
    """
1006
    obj = super(NodeGroup, cls).FromDict(val)
1007
    obj.members = []
1008
    return obj
1009

    
1010
  def UpgradeConfig(self):
1011
    """Fill defaults for missing configuration values.
1012

1013
    """
1014
    if self.ndparams is None:
1015
      self.ndparams = {}
1016

    
1017
    if self.serial_no is None:
1018
      self.serial_no = 1
1019

    
1020
    if self.alloc_policy is None:
1021
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1022

    
1023
    # We only update mtime, and not ctime, since we would not be able to provide
1024
    # a correct value for creation time.
1025
    if self.mtime is None:
1026
      self.mtime = time.time()
1027

    
1028
  def FillND(self, node):
1029
    """Return filled out ndparams for L{object.Node}
1030

1031
    @type node: L{objects.Node}
1032
    @param node: A Node object to fill
1033
    @return a copy of the node's ndparams with defaults filled
1034

1035
    """
1036
    return self.SimpleFillND(node.ndparams)
1037

    
1038
  def SimpleFillND(self, ndparams):
1039
    """Fill a given ndparams dict with defaults.
1040

1041
    @type ndparams: dict
1042
    @param ndparams: the dict to fill
1043
    @rtype: dict
1044
    @return: a copy of the passed in ndparams with missing keys filled
1045
        from the node group defaults
1046

1047
    """
1048
    return FillDict(self.ndparams, ndparams)
1049

    
1050

    
1051
class Cluster(TaggableObject):
1052
  """Config object representing the cluster."""
1053
  __slots__ = [
1054
    "serial_no",
1055
    "rsahostkeypub",
1056
    "highest_used_port",
1057
    "tcpudp_port_pool",
1058
    "mac_prefix",
1059
    "volume_group_name",
1060
    "reserved_lvs",
1061
    "drbd_usermode_helper",
1062
    "default_bridge",
1063
    "default_hypervisor",
1064
    "master_node",
1065
    "master_ip",
1066
    "master_netdev",
1067
    "cluster_name",
1068
    "file_storage_dir",
1069
    "enabled_hypervisors",
1070
    "hvparams",
1071
    "os_hvp",
1072
    "beparams",
1073
    "osparams",
1074
    "nicparams",
1075
    "ndparams",
1076
    "candidate_pool_size",
1077
    "modify_etc_hosts",
1078
    "modify_ssh_setup",
1079
    "maintain_node_health",
1080
    "uid_pool",
1081
    "default_iallocator",
1082
    "hidden_os",
1083
    "blacklisted_os",
1084
    "primary_ip_family",
1085
    "prealloc_wipe_disks",
1086
    ] + _TIMESTAMPS + _UUID
1087

    
1088
  def UpgradeConfig(self):
1089
    """Fill defaults for missing configuration values.
1090

1091
    """
1092
    # pylint: disable-msg=E0203
1093
    # because these are "defined" via slots, not manually
1094
    if self.hvparams is None:
1095
      self.hvparams = constants.HVC_DEFAULTS
1096
    else:
1097
      for hypervisor in self.hvparams:
1098
        self.hvparams[hypervisor] = FillDict(
1099
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1100

    
1101
    if self.os_hvp is None:
1102
      self.os_hvp = {}
1103

    
1104
    # osparams added before 2.2
1105
    if self.osparams is None:
1106
      self.osparams = {}
1107

    
1108
    if self.ndparams is None:
1109
      self.ndparams = constants.NDC_DEFAULTS
1110

    
1111
    self.beparams = UpgradeGroupedParams(self.beparams,
1112
                                         constants.BEC_DEFAULTS)
1113
    migrate_default_bridge = not self.nicparams
1114
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1115
                                          constants.NICC_DEFAULTS)
1116
    if migrate_default_bridge:
1117
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1118
        self.default_bridge
1119

    
1120
    if self.modify_etc_hosts is None:
1121
      self.modify_etc_hosts = True
1122

    
1123
    if self.modify_ssh_setup is None:
1124
      self.modify_ssh_setup = True
1125

    
1126
    # default_bridge is no longer used it 2.1. The slot is left there to
1127
    # support auto-upgrading. It can be removed once we decide to deprecate
1128
    # upgrading straight from 2.0.
1129
    if self.default_bridge is not None:
1130
      self.default_bridge = None
1131

    
1132
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1133
    # code can be removed once upgrading straight from 2.0 is deprecated.
1134
    if self.default_hypervisor is not None:
1135
      self.enabled_hypervisors = ([self.default_hypervisor] +
1136
        [hvname for hvname in self.enabled_hypervisors
1137
         if hvname != self.default_hypervisor])
1138
      self.default_hypervisor = None
1139

    
1140
    # maintain_node_health added after 2.1.1
1141
    if self.maintain_node_health is None:
1142
      self.maintain_node_health = False
1143

    
1144
    if self.uid_pool is None:
1145
      self.uid_pool = []
1146

    
1147
    if self.default_iallocator is None:
1148
      self.default_iallocator = ""
1149

    
1150
    # reserved_lvs added before 2.2
1151
    if self.reserved_lvs is None:
1152
      self.reserved_lvs = []
1153

    
1154
    # hidden and blacklisted operating systems added before 2.2.1
1155
    if self.hidden_os is None:
1156
      self.hidden_os = []
1157

    
1158
    if self.blacklisted_os is None:
1159
      self.blacklisted_os = []
1160

    
1161
    # primary_ip_family added before 2.3
1162
    if self.primary_ip_family is None:
1163
      self.primary_ip_family = AF_INET
1164

    
1165
    if self.prealloc_wipe_disks is None:
1166
      self.prealloc_wipe_disks = False
1167

    
1168
  def ToDict(self):
1169
    """Custom function for cluster.
1170

1171
    """
1172
    mydict = super(Cluster, self).ToDict()
1173
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1174
    return mydict
1175

    
1176
  @classmethod
1177
  def FromDict(cls, val):
1178
    """Custom function for cluster.
1179

1180
    """
1181
    obj = super(Cluster, cls).FromDict(val)
1182
    if not isinstance(obj.tcpudp_port_pool, set):
1183
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1184
    return obj
1185

    
1186
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1187
    """Get the default hypervisor parameters for the cluster.
1188

1189
    @param hypervisor: the hypervisor name
1190
    @param os_name: if specified, we'll also update the defaults for this OS
1191
    @param skip_keys: if passed, list of keys not to use
1192
    @return: the defaults dict
1193

1194
    """
1195
    if skip_keys is None:
1196
      skip_keys = []
1197

    
1198
    fill_stack = [self.hvparams.get(hypervisor, {})]
1199
    if os_name is not None:
1200
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1201
      fill_stack.append(os_hvp)
1202

    
1203
    ret_dict = {}
1204
    for o_dict in fill_stack:
1205
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1206

    
1207
    return ret_dict
1208

    
1209
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1210
    """Fill a given hvparams dict with cluster defaults.
1211

1212
    @type hv_name: string
1213
    @param hv_name: the hypervisor to use
1214
    @type os_name: string
1215
    @param os_name: the OS to use for overriding the hypervisor defaults
1216
    @type skip_globals: boolean
1217
    @param skip_globals: if True, the global hypervisor parameters will
1218
        not be filled
1219
    @rtype: dict
1220
    @return: a copy of the given hvparams with missing keys filled from
1221
        the cluster defaults
1222

1223
    """
1224
    if skip_globals:
1225
      skip_keys = constants.HVC_GLOBALS
1226
    else:
1227
      skip_keys = []
1228

    
1229
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1230
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1231

    
1232
  def FillHV(self, instance, skip_globals=False):
1233
    """Fill an instance's hvparams dict with cluster defaults.
1234

1235
    @type instance: L{objects.Instance}
1236
    @param instance: the instance parameter to fill
1237
    @type skip_globals: boolean
1238
    @param skip_globals: if True, the global hypervisor parameters will
1239
        not be filled
1240
    @rtype: dict
1241
    @return: a copy of the instance's hvparams with missing keys filled from
1242
        the cluster defaults
1243

1244
    """
1245
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1246
                             instance.hvparams, skip_globals)
1247

    
1248
  def SimpleFillBE(self, beparams):
1249
    """Fill a given beparams dict with cluster defaults.
1250

1251
    @type beparams: dict
1252
    @param beparams: the dict to fill
1253
    @rtype: dict
1254
    @return: a copy of the passed in beparams with missing keys filled
1255
        from the cluster defaults
1256

1257
    """
1258
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1259

    
1260
  def FillBE(self, instance):
1261
    """Fill an instance's beparams dict with cluster defaults.
1262

1263
    @type instance: L{objects.Instance}
1264
    @param instance: the instance parameter to fill
1265
    @rtype: dict
1266
    @return: a copy of the instance's beparams with missing keys filled from
1267
        the cluster defaults
1268

1269
    """
1270
    return self.SimpleFillBE(instance.beparams)
1271

    
1272
  def SimpleFillNIC(self, nicparams):
1273
    """Fill a given nicparams dict with cluster defaults.
1274

1275
    @type nicparams: dict
1276
    @param nicparams: the dict to fill
1277
    @rtype: dict
1278
    @return: a copy of the passed in nicparams with missing keys filled
1279
        from the cluster defaults
1280

1281
    """
1282
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1283

    
1284
  def SimpleFillOS(self, os_name, os_params):
1285
    """Fill an instance's osparams dict with cluster defaults.
1286

1287
    @type os_name: string
1288
    @param os_name: the OS name to use
1289
    @type os_params: dict
1290
    @param os_params: the dict to fill with default values
1291
    @rtype: dict
1292
    @return: a copy of the instance's osparams with missing keys filled from
1293
        the cluster defaults
1294

1295
    """
1296
    name_only = os_name.split("+", 1)[0]
1297
    # base OS
1298
    result = self.osparams.get(name_only, {})
1299
    # OS with variant
1300
    result = FillDict(result, self.osparams.get(os_name, {}))
1301
    # specified params
1302
    return FillDict(result, os_params)
1303

    
1304
  def FillND(self, node, nodegroup):
1305
    """Return filled out ndparams for L{objects.NodeGroup} and L{object.Node}
1306

1307
    @type node: L{objects.Node}
1308
    @param node: A Node object to fill
1309
    @type nodegroup: L{objects.NodeGroup}
1310
    @param nodegroup: A Node object to fill
1311
    @return a copy of the node's ndparams with defaults filled
1312

1313
    """
1314
    return self.SimpleFillND(nodegroup.FillND(node))
1315

    
1316
  def SimpleFillND(self, ndparams):
1317
    """Fill a given ndparams dict with defaults.
1318

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

1325
    """
1326
    return FillDict(self.ndparams, ndparams)
1327

    
1328

    
1329
class BlockDevStatus(ConfigObject):
1330
  """Config object representing the status of a block device."""
1331
  __slots__ = [
1332
    "dev_path",
1333
    "major",
1334
    "minor",
1335
    "sync_percent",
1336
    "estimated_time",
1337
    "is_degraded",
1338
    "ldisk_status",
1339
    ]
1340

    
1341

    
1342
class ImportExportStatus(ConfigObject):
1343
  """Config object representing the status of an import or export."""
1344
  __slots__ = [
1345
    "recent_output",
1346
    "listen_port",
1347
    "connected",
1348
    "progress_mbytes",
1349
    "progress_throughput",
1350
    "progress_eta",
1351
    "progress_percent",
1352
    "exit_status",
1353
    "error_message",
1354
    ] + _TIMESTAMPS
1355

    
1356

    
1357
class ImportExportOptions(ConfigObject):
1358
  """Options for import/export daemon
1359

1360
  @ivar key_name: X509 key name (None for cluster certificate)
1361
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1362
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1363
  @ivar magic: Used to ensure the connection goes to the right disk
1364
  @ivar ipv6: Whether to use IPv6
1365
  @ivar connect_timeout: Number of seconds for establishing connection
1366

1367
  """
1368
  __slots__ = [
1369
    "key_name",
1370
    "ca_pem",
1371
    "compress",
1372
    "magic",
1373
    "ipv6",
1374
    "connect_timeout",
1375
    ]
1376

    
1377

    
1378
class ConfdRequest(ConfigObject):
1379
  """Object holding a confd request.
1380

1381
  @ivar protocol: confd protocol version
1382
  @ivar type: confd query type
1383
  @ivar query: query request
1384
  @ivar rsalt: requested reply salt
1385

1386
  """
1387
  __slots__ = [
1388
    "protocol",
1389
    "type",
1390
    "query",
1391
    "rsalt",
1392
    ]
1393

    
1394

    
1395
class ConfdReply(ConfigObject):
1396
  """Object holding a confd reply.
1397

1398
  @ivar protocol: confd protocol version
1399
  @ivar status: reply status code (ok, error)
1400
  @ivar answer: confd query reply
1401
  @ivar serial: configuration serial number
1402

1403
  """
1404
  __slots__ = [
1405
    "protocol",
1406
    "status",
1407
    "answer",
1408
    "serial",
1409
    ]
1410

    
1411

    
1412
class QueryFieldDefinition(ConfigObject):
1413
  """Object holding a query field definition.
1414

1415
  @ivar name: Field name
1416
  @ivar title: Human-readable title
1417
  @ivar kind: Field type
1418
  @ivar doc: Human-readable description
1419

1420
  """
1421
  __slots__ = [
1422
    "name",
1423
    "title",
1424
    "kind",
1425
    "doc",
1426
    ]
1427

    
1428

    
1429
class _QueryResponseBase(ConfigObject):
1430
  __slots__ = [
1431
    "fields",
1432
    ]
1433

    
1434
  def ToDict(self):
1435
    """Custom function for serializing.
1436

1437
    """
1438
    mydict = super(_QueryResponseBase, self).ToDict()
1439
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1440
    return mydict
1441

    
1442
  @classmethod
1443
  def FromDict(cls, val):
1444
    """Custom function for de-serializing.
1445

1446
    """
1447
    obj = super(_QueryResponseBase, cls).FromDict(val)
1448
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1449
    return obj
1450

    
1451

    
1452
class QueryRequest(ConfigObject):
1453
  """Object holding a query request.
1454

1455
  """
1456
  __slots__ = [
1457
    "what",
1458
    "fields",
1459
    "filter",
1460
    ]
1461

    
1462

    
1463
class QueryResponse(_QueryResponseBase):
1464
  """Object holding the response to a query.
1465

1466
  @ivar fields: List of L{QueryFieldDefinition} objects
1467
  @ivar data: Requested data
1468

1469
  """
1470
  __slots__ = [
1471
    "data",
1472
    ]
1473

    
1474

    
1475
class QueryFieldsRequest(ConfigObject):
1476
  """Object holding a request for querying available fields.
1477

1478
  """
1479
  __slots__ = [
1480
    "what",
1481
    "fields",
1482
    ]
1483

    
1484

    
1485
class QueryFieldsResponse(_QueryResponseBase):
1486
  """Object holding the response to a query for fields.
1487

1488
  @ivar fields: List of L{QueryFieldDefinition} objects
1489

1490
  """
1491
  __slots__ = [
1492
    ]
1493

    
1494

    
1495
class InstanceConsole(ConfigObject):
1496
  """Object describing how to access the console of an instance.
1497

1498
  """
1499
  __slots__ = [
1500
    "instance",
1501
    "kind",
1502
    "message",
1503
    "host",
1504
    "port",
1505
    "user",
1506
    "command",
1507
    "display",
1508
    ]
1509

    
1510
  def Validate(self):
1511
    """Validates contents of this object.
1512

1513
    """
1514
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1515
    assert self.instance, "Missing instance name"
1516
    assert self.message or self.kind in [constants.CONS_SSH, constants.CONS_VNC]
1517
    assert self.host or self.kind == constants.CONS_MESSAGE
1518
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1519
                                      constants.CONS_SSH]
1520
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1521
                                      constants.CONS_VNC]
1522
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1523
                                         constants.CONS_VNC]
1524
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1525
                                         constants.CONS_SSH]
1526
    return True
1527

    
1528

    
1529
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1530
  """Simple wrapper over ConfigParse that allows serialization.
1531

1532
  This class is basically ConfigParser.SafeConfigParser with two
1533
  additional methods that allow it to serialize/unserialize to/from a
1534
  buffer.
1535

1536
  """
1537
  def Dumps(self):
1538
    """Dump this instance and return the string representation."""
1539
    buf = StringIO()
1540
    self.write(buf)
1541
    return buf.getvalue()
1542

    
1543
  @classmethod
1544
  def Loads(cls, data):
1545
    """Load data from a string."""
1546
    buf = StringIO(data)
1547
    cfp = cls()
1548
    cfp.readfp(buf)
1549
    return cfp