Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 5f06ce5e

History | View | Annotate | Download (47.5 kB)

1
#
2
#
3

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

    
21

    
22
"""Transportable objects for Ganeti.
23

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

27
"""
28

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

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

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

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

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

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

    
48
from socket import AF_INET
49

    
50

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

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

    
57

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

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

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

    
81

    
82
def UpgradeGroupedParams(target, defaults):
83
  """Update all groups for the target parameter.
84

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

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

    
98

    
99
def UpgradeBeParams(target):
100
  """Update the be parameters dict to the new format.
101

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

105
  """
106
  if constants.BE_MEMORY in target:
107
    memory = target[constants.BE_MEMORY]
108
    target[constants.BE_MAXMEM] = memory
109
    target[constants.BE_MINMEM] = memory
110
    del target[constants.BE_MEMORY]
111

    
112

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

116
  It has the following properties:
117

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

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

125
  """
126
  __slots__ = []
127

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

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

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

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

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

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

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

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

    
171
  __getstate__ = ToDict
172

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

252
    """
253
    pass
254

    
255

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

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

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

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

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

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

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

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

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

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

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

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

314
    This replaces the tags set with a list.
315

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

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

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

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

    
334

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

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

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

    
353

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

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

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

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

    
377
    return mydict
378

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

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

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

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

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

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

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

    
426

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

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

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

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

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

    
450

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574
    This only works for VG-based disks.
575

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
750

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

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

772
    This is a simple wrapper over _ComputeAllNodes.
773

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
853
    return ret
854

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

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

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

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

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

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

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

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

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

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

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

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

    
931

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

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

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

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

    
956
  VARIANT_DELIM = "+"
957

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

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

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

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

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

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

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

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

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

    
991

    
992
class NodeHvState(ConfigObject):
993
  """Hypvervisor state on a node.
994

995
  @ivar mem_total: Total amount of memory
996
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
997
    available)
998
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
999
    rounding
1000
  @ivar mem_inst: Memory used by instances living on node
1001
  @ivar cpu_total: Total node CPU core count
1002
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1003

1004
  """
1005
  __slots__ = [
1006
    "mem_total",
1007
    "mem_node",
1008
    "mem_hv",
1009
    "mem_inst",
1010
    "cpu_total",
1011
    "cpu_node",
1012
    ] + _TIMESTAMPS
1013

    
1014

    
1015
class NodeDiskState(ConfigObject):
1016
  """Disk state on a node.
1017

1018
  """
1019
  __slots__ = [
1020
    "total",
1021
    "reserved",
1022
    "overhead",
1023
    ] + _TIMESTAMPS
1024

    
1025

    
1026
class Node(TaggableObject):
1027
  """Config object representing a node.
1028

1029
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1030
  @ivar hv_state_static: Hypervisor state overriden by user
1031
  @ivar disk_state: Disk state (e.g. free space)
1032
  @ivar disk_state_static: Disk state overriden by user
1033

1034
  """
1035
  __slots__ = [
1036
    "name",
1037
    "primary_ip",
1038
    "secondary_ip",
1039
    "serial_no",
1040
    "master_candidate",
1041
    "offline",
1042
    "drained",
1043
    "group",
1044
    "master_capable",
1045
    "vm_capable",
1046
    "ndparams",
1047
    "powered",
1048
    "hv_state",
1049
    "hv_state_static",
1050
    "disk_state",
1051
    "disk_state_static",
1052
    ] + _TIMESTAMPS + _UUID
1053

    
1054
  def UpgradeConfig(self):
1055
    """Fill defaults for missing configuration values.
1056

1057
    """
1058
    # pylint: disable=E0203
1059
    # because these are "defined" via slots, not manually
1060
    if self.master_capable is None:
1061
      self.master_capable = True
1062

    
1063
    if self.vm_capable is None:
1064
      self.vm_capable = True
1065

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

    
1069
    if self.powered is None:
1070
      self.powered = True
1071

    
1072
  def ToDict(self):
1073
    """Custom function for serializing.
1074

1075
    """
1076
    data = super(Node, self).ToDict()
1077

    
1078
    hv_state = data.get("hv_state", None)
1079
    if hv_state is not None:
1080
      data["hv_state"] = self._ContainerToDicts(hv_state)
1081

    
1082
    disk_state = data.get("disk_state", None)
1083
    if disk_state is not None:
1084
      data["disk_state"] = \
1085
        dict((key, self._ContainerToDicts(value))
1086
             for (key, value) in disk_state.items())
1087

    
1088
    return data
1089

    
1090
  @classmethod
1091
  def FromDict(cls, val):
1092
    """Custom function for deserializing.
1093

1094
    """
1095
    obj = super(Node, cls).FromDict(val)
1096

    
1097
    if obj.hv_state is not None:
1098
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1099

    
1100
    if obj.disk_state is not None:
1101
      obj.disk_state = \
1102
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1103
             for (key, value) in obj.disk_state.items())
1104

    
1105
    return obj
1106

    
1107

    
1108
class NodeGroup(TaggableObject):
1109
  """Config object representing a node group."""
1110
  __slots__ = [
1111
    "name",
1112
    "members",
1113
    "ndparams",
1114
    "serial_no",
1115
    "alloc_policy",
1116
    ] + _TIMESTAMPS + _UUID
1117

    
1118
  def ToDict(self):
1119
    """Custom function for nodegroup.
1120

1121
    This discards the members object, which gets recalculated and is only kept
1122
    in memory.
1123

1124
    """
1125
    mydict = super(NodeGroup, self).ToDict()
1126
    del mydict["members"]
1127
    return mydict
1128

    
1129
  @classmethod
1130
  def FromDict(cls, val):
1131
    """Custom function for nodegroup.
1132

1133
    The members slot is initialized to an empty list, upon deserialization.
1134

1135
    """
1136
    obj = super(NodeGroup, cls).FromDict(val)
1137
    obj.members = []
1138
    return obj
1139

    
1140
  def UpgradeConfig(self):
1141
    """Fill defaults for missing configuration values.
1142

1143
    """
1144
    if self.ndparams is None:
1145
      self.ndparams = {}
1146

    
1147
    if self.serial_no is None:
1148
      self.serial_no = 1
1149

    
1150
    if self.alloc_policy is None:
1151
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1152

    
1153
    # We only update mtime, and not ctime, since we would not be able to provide
1154
    # a correct value for creation time.
1155
    if self.mtime is None:
1156
      self.mtime = time.time()
1157

    
1158
  def FillND(self, node):
1159
    """Return filled out ndparams for L{objects.Node}
1160

1161
    @type node: L{objects.Node}
1162
    @param node: A Node object to fill
1163
    @return a copy of the node's ndparams with defaults filled
1164

1165
    """
1166
    return self.SimpleFillND(node.ndparams)
1167

    
1168
  def SimpleFillND(self, ndparams):
1169
    """Fill a given ndparams dict with defaults.
1170

1171
    @type ndparams: dict
1172
    @param ndparams: the dict to fill
1173
    @rtype: dict
1174
    @return: a copy of the passed in ndparams with missing keys filled
1175
        from the node group defaults
1176

1177
    """
1178
    return FillDict(self.ndparams, ndparams)
1179

    
1180

    
1181
class Cluster(TaggableObject):
1182
  """Config object representing the cluster."""
1183
  __slots__ = [
1184
    "serial_no",
1185
    "rsahostkeypub",
1186
    "highest_used_port",
1187
    "tcpudp_port_pool",
1188
    "mac_prefix",
1189
    "volume_group_name",
1190
    "reserved_lvs",
1191
    "drbd_usermode_helper",
1192
    "default_bridge",
1193
    "default_hypervisor",
1194
    "master_node",
1195
    "master_ip",
1196
    "master_netdev",
1197
    "master_netmask",
1198
    "use_external_mip_script",
1199
    "cluster_name",
1200
    "file_storage_dir",
1201
    "shared_file_storage_dir",
1202
    "enabled_hypervisors",
1203
    "hvparams",
1204
    "os_hvp",
1205
    "beparams",
1206
    "osparams",
1207
    "nicparams",
1208
    "ndparams",
1209
    "candidate_pool_size",
1210
    "modify_etc_hosts",
1211
    "modify_ssh_setup",
1212
    "maintain_node_health",
1213
    "uid_pool",
1214
    "default_iallocator",
1215
    "hidden_os",
1216
    "blacklisted_os",
1217
    "primary_ip_family",
1218
    "prealloc_wipe_disks",
1219
    ] + _TIMESTAMPS + _UUID
1220

    
1221
  def UpgradeConfig(self):
1222
    """Fill defaults for missing configuration values.
1223

1224
    """
1225
    # pylint: disable=E0203
1226
    # because these are "defined" via slots, not manually
1227
    if self.hvparams is None:
1228
      self.hvparams = constants.HVC_DEFAULTS
1229
    else:
1230
      for hypervisor in self.hvparams:
1231
        self.hvparams[hypervisor] = FillDict(
1232
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1233

    
1234
    if self.os_hvp is None:
1235
      self.os_hvp = {}
1236

    
1237
    # osparams added before 2.2
1238
    if self.osparams is None:
1239
      self.osparams = {}
1240

    
1241
    if self.ndparams is None:
1242
      self.ndparams = constants.NDC_DEFAULTS
1243

    
1244
    self.beparams = UpgradeGroupedParams(self.beparams,
1245
                                         constants.BEC_DEFAULTS)
1246
    for beparams_group in self.beparams:
1247
      UpgradeBeParams(self.beparams[beparams_group])
1248

    
1249
    migrate_default_bridge = not self.nicparams
1250
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1251
                                          constants.NICC_DEFAULTS)
1252
    if migrate_default_bridge:
1253
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1254
        self.default_bridge
1255

    
1256
    if self.modify_etc_hosts is None:
1257
      self.modify_etc_hosts = True
1258

    
1259
    if self.modify_ssh_setup is None:
1260
      self.modify_ssh_setup = True
1261

    
1262
    # default_bridge is no longer used in 2.1. The slot is left there to
1263
    # support auto-upgrading. It can be removed once we decide to deprecate
1264
    # upgrading straight from 2.0.
1265
    if self.default_bridge is not None:
1266
      self.default_bridge = None
1267

    
1268
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1269
    # code can be removed once upgrading straight from 2.0 is deprecated.
1270
    if self.default_hypervisor is not None:
1271
      self.enabled_hypervisors = ([self.default_hypervisor] +
1272
        [hvname for hvname in self.enabled_hypervisors
1273
         if hvname != self.default_hypervisor])
1274
      self.default_hypervisor = None
1275

    
1276
    # maintain_node_health added after 2.1.1
1277
    if self.maintain_node_health is None:
1278
      self.maintain_node_health = False
1279

    
1280
    if self.uid_pool is None:
1281
      self.uid_pool = []
1282

    
1283
    if self.default_iallocator is None:
1284
      self.default_iallocator = ""
1285

    
1286
    # reserved_lvs added before 2.2
1287
    if self.reserved_lvs is None:
1288
      self.reserved_lvs = []
1289

    
1290
    # hidden and blacklisted operating systems added before 2.2.1
1291
    if self.hidden_os is None:
1292
      self.hidden_os = []
1293

    
1294
    if self.blacklisted_os is None:
1295
      self.blacklisted_os = []
1296

    
1297
    # primary_ip_family added before 2.3
1298
    if self.primary_ip_family is None:
1299
      self.primary_ip_family = AF_INET
1300

    
1301
    if self.master_netmask is None:
1302
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1303
      self.master_netmask = ipcls.iplen
1304

    
1305
    if self.prealloc_wipe_disks is None:
1306
      self.prealloc_wipe_disks = False
1307

    
1308
    # shared_file_storage_dir added before 2.5
1309
    if self.shared_file_storage_dir is None:
1310
      self.shared_file_storage_dir = ""
1311

    
1312
    if self.use_external_mip_script is None:
1313
      self.use_external_mip_script = False
1314

    
1315
  def ToDict(self):
1316
    """Custom function for cluster.
1317

1318
    """
1319
    mydict = super(Cluster, self).ToDict()
1320
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1321
    return mydict
1322

    
1323
  @classmethod
1324
  def FromDict(cls, val):
1325
    """Custom function for cluster.
1326

1327
    """
1328
    obj = super(Cluster, cls).FromDict(val)
1329
    if not isinstance(obj.tcpudp_port_pool, set):
1330
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1331
    return obj
1332

    
1333
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1334
    """Get the default hypervisor parameters for the cluster.
1335

1336
    @param hypervisor: the hypervisor name
1337
    @param os_name: if specified, we'll also update the defaults for this OS
1338
    @param skip_keys: if passed, list of keys not to use
1339
    @return: the defaults dict
1340

1341
    """
1342
    if skip_keys is None:
1343
      skip_keys = []
1344

    
1345
    fill_stack = [self.hvparams.get(hypervisor, {})]
1346
    if os_name is not None:
1347
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1348
      fill_stack.append(os_hvp)
1349

    
1350
    ret_dict = {}
1351
    for o_dict in fill_stack:
1352
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1353

    
1354
    return ret_dict
1355

    
1356
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1357
    """Fill a given hvparams dict with cluster defaults.
1358

1359
    @type hv_name: string
1360
    @param hv_name: the hypervisor to use
1361
    @type os_name: string
1362
    @param os_name: the OS to use for overriding the hypervisor defaults
1363
    @type skip_globals: boolean
1364
    @param skip_globals: if True, the global hypervisor parameters will
1365
        not be filled
1366
    @rtype: dict
1367
    @return: a copy of the given hvparams with missing keys filled from
1368
        the cluster defaults
1369

1370
    """
1371
    if skip_globals:
1372
      skip_keys = constants.HVC_GLOBALS
1373
    else:
1374
      skip_keys = []
1375

    
1376
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1377
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1378

    
1379
  def FillHV(self, instance, skip_globals=False):
1380
    """Fill an instance's hvparams dict with cluster defaults.
1381

1382
    @type instance: L{objects.Instance}
1383
    @param instance: the instance parameter to fill
1384
    @type skip_globals: boolean
1385
    @param skip_globals: if True, the global hypervisor parameters will
1386
        not be filled
1387
    @rtype: dict
1388
    @return: a copy of the instance's hvparams with missing keys filled from
1389
        the cluster defaults
1390

1391
    """
1392
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1393
                             instance.hvparams, skip_globals)
1394

    
1395
  def SimpleFillBE(self, beparams):
1396
    """Fill a given beparams dict with cluster defaults.
1397

1398
    @type beparams: dict
1399
    @param beparams: the dict to fill
1400
    @rtype: dict
1401
    @return: a copy of the passed in beparams with missing keys filled
1402
        from the cluster defaults
1403

1404
    """
1405
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1406

    
1407
  def FillBE(self, instance):
1408
    """Fill an instance's beparams dict with cluster defaults.
1409

1410
    @type instance: L{objects.Instance}
1411
    @param instance: the instance parameter to fill
1412
    @rtype: dict
1413
    @return: a copy of the instance's beparams with missing keys filled from
1414
        the cluster defaults
1415

1416
    """
1417
    return self.SimpleFillBE(instance.beparams)
1418

    
1419
  def SimpleFillNIC(self, nicparams):
1420
    """Fill a given nicparams dict with cluster defaults.
1421

1422
    @type nicparams: dict
1423
    @param nicparams: the dict to fill
1424
    @rtype: dict
1425
    @return: a copy of the passed in nicparams with missing keys filled
1426
        from the cluster defaults
1427

1428
    """
1429
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1430

    
1431
  def SimpleFillOS(self, os_name, os_params):
1432
    """Fill an instance's osparams dict with cluster defaults.
1433

1434
    @type os_name: string
1435
    @param os_name: the OS name to use
1436
    @type os_params: dict
1437
    @param os_params: the dict to fill with default values
1438
    @rtype: dict
1439
    @return: a copy of the instance's osparams with missing keys filled from
1440
        the cluster defaults
1441

1442
    """
1443
    name_only = os_name.split("+", 1)[0]
1444
    # base OS
1445
    result = self.osparams.get(name_only, {})
1446
    # OS with variant
1447
    result = FillDict(result, self.osparams.get(os_name, {}))
1448
    # specified params
1449
    return FillDict(result, os_params)
1450

    
1451
  def FillND(self, node, nodegroup):
1452
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1453

1454
    @type node: L{objects.Node}
1455
    @param node: A Node object to fill
1456
    @type nodegroup: L{objects.NodeGroup}
1457
    @param nodegroup: A Node object to fill
1458
    @return a copy of the node's ndparams with defaults filled
1459

1460
    """
1461
    return self.SimpleFillND(nodegroup.FillND(node))
1462

    
1463
  def SimpleFillND(self, ndparams):
1464
    """Fill a given ndparams dict with defaults.
1465

1466
    @type ndparams: dict
1467
    @param ndparams: the dict to fill
1468
    @rtype: dict
1469
    @return: a copy of the passed in ndparams with missing keys filled
1470
        from the cluster defaults
1471

1472
    """
1473
    return FillDict(self.ndparams, ndparams)
1474

    
1475

    
1476
class BlockDevStatus(ConfigObject):
1477
  """Config object representing the status of a block device."""
1478
  __slots__ = [
1479
    "dev_path",
1480
    "major",
1481
    "minor",
1482
    "sync_percent",
1483
    "estimated_time",
1484
    "is_degraded",
1485
    "ldisk_status",
1486
    ]
1487

    
1488

    
1489
class ImportExportStatus(ConfigObject):
1490
  """Config object representing the status of an import or export."""
1491
  __slots__ = [
1492
    "recent_output",
1493
    "listen_port",
1494
    "connected",
1495
    "progress_mbytes",
1496
    "progress_throughput",
1497
    "progress_eta",
1498
    "progress_percent",
1499
    "exit_status",
1500
    "error_message",
1501
    ] + _TIMESTAMPS
1502

    
1503

    
1504
class ImportExportOptions(ConfigObject):
1505
  """Options for import/export daemon
1506

1507
  @ivar key_name: X509 key name (None for cluster certificate)
1508
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1509
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1510
  @ivar magic: Used to ensure the connection goes to the right disk
1511
  @ivar ipv6: Whether to use IPv6
1512
  @ivar connect_timeout: Number of seconds for establishing connection
1513

1514
  """
1515
  __slots__ = [
1516
    "key_name",
1517
    "ca_pem",
1518
    "compress",
1519
    "magic",
1520
    "ipv6",
1521
    "connect_timeout",
1522
    ]
1523

    
1524

    
1525
class ConfdRequest(ConfigObject):
1526
  """Object holding a confd request.
1527

1528
  @ivar protocol: confd protocol version
1529
  @ivar type: confd query type
1530
  @ivar query: query request
1531
  @ivar rsalt: requested reply salt
1532

1533
  """
1534
  __slots__ = [
1535
    "protocol",
1536
    "type",
1537
    "query",
1538
    "rsalt",
1539
    ]
1540

    
1541

    
1542
class ConfdReply(ConfigObject):
1543
  """Object holding a confd reply.
1544

1545
  @ivar protocol: confd protocol version
1546
  @ivar status: reply status code (ok, error)
1547
  @ivar answer: confd query reply
1548
  @ivar serial: configuration serial number
1549

1550
  """
1551
  __slots__ = [
1552
    "protocol",
1553
    "status",
1554
    "answer",
1555
    "serial",
1556
    ]
1557

    
1558

    
1559
class QueryFieldDefinition(ConfigObject):
1560
  """Object holding a query field definition.
1561

1562
  @ivar name: Field name
1563
  @ivar title: Human-readable title
1564
  @ivar kind: Field type
1565
  @ivar doc: Human-readable description
1566

1567
  """
1568
  __slots__ = [
1569
    "name",
1570
    "title",
1571
    "kind",
1572
    "doc",
1573
    ]
1574

    
1575

    
1576
class _QueryResponseBase(ConfigObject):
1577
  __slots__ = [
1578
    "fields",
1579
    ]
1580

    
1581
  def ToDict(self):
1582
    """Custom function for serializing.
1583

1584
    """
1585
    mydict = super(_QueryResponseBase, self).ToDict()
1586
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1587
    return mydict
1588

    
1589
  @classmethod
1590
  def FromDict(cls, val):
1591
    """Custom function for de-serializing.
1592

1593
    """
1594
    obj = super(_QueryResponseBase, cls).FromDict(val)
1595
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1596
    return obj
1597

    
1598

    
1599
class QueryRequest(ConfigObject):
1600
  """Object holding a query request.
1601

1602
  """
1603
  __slots__ = [
1604
    "what",
1605
    "fields",
1606
    "qfilter",
1607
    ]
1608

    
1609

    
1610
class QueryResponse(_QueryResponseBase):
1611
  """Object holding the response to a query.
1612

1613
  @ivar fields: List of L{QueryFieldDefinition} objects
1614
  @ivar data: Requested data
1615

1616
  """
1617
  __slots__ = [
1618
    "data",
1619
    ]
1620

    
1621

    
1622
class QueryFieldsRequest(ConfigObject):
1623
  """Object holding a request for querying available fields.
1624

1625
  """
1626
  __slots__ = [
1627
    "what",
1628
    "fields",
1629
    ]
1630

    
1631

    
1632
class QueryFieldsResponse(_QueryResponseBase):
1633
  """Object holding the response to a query for fields.
1634

1635
  @ivar fields: List of L{QueryFieldDefinition} objects
1636

1637
  """
1638
  __slots__ = [
1639
    ]
1640

    
1641

    
1642
class MigrationStatus(ConfigObject):
1643
  """Object holding the status of a migration.
1644

1645
  """
1646
  __slots__ = [
1647
    "status",
1648
    "transferred_ram",
1649
    "total_ram",
1650
    ]
1651

    
1652

    
1653
class InstanceConsole(ConfigObject):
1654
  """Object describing how to access the console of an instance.
1655

1656
  """
1657
  __slots__ = [
1658
    "instance",
1659
    "kind",
1660
    "message",
1661
    "host",
1662
    "port",
1663
    "user",
1664
    "command",
1665
    "display",
1666
    ]
1667

    
1668
  def Validate(self):
1669
    """Validates contents of this object.
1670

1671
    """
1672
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1673
    assert self.instance, "Missing instance name"
1674
    assert self.message or self.kind in [constants.CONS_SSH,
1675
                                         constants.CONS_SPICE,
1676
                                         constants.CONS_VNC]
1677
    assert self.host or self.kind == constants.CONS_MESSAGE
1678
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1679
                                      constants.CONS_SSH]
1680
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1681
                                      constants.CONS_SPICE,
1682
                                      constants.CONS_VNC]
1683
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1684
                                         constants.CONS_SPICE,
1685
                                         constants.CONS_VNC]
1686
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1687
                                         constants.CONS_SPICE,
1688
                                         constants.CONS_SSH]
1689
    return True
1690

    
1691

    
1692
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1693
  """Simple wrapper over ConfigParse that allows serialization.
1694

1695
  This class is basically ConfigParser.SafeConfigParser with two
1696
  additional methods that allow it to serialize/unserialize to/from a
1697
  buffer.
1698

1699
  """
1700
  def Dumps(self):
1701
    """Dump this instance and return the string representation."""
1702
    buf = StringIO()
1703
    self.write(buf)
1704
    return buf.getvalue()
1705

    
1706
  @classmethod
1707
  def Loads(cls, data):
1708
    """Load data from a string."""
1709
    buf = StringIO(data)
1710
    cfp = cls()
1711
    cfp.readfp(buf)
1712
    return cfp