Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 57dc299a

History | View | Annotate | Download (54.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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
from ganeti import utils
48

    
49
from socket import AF_INET
50

    
51

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

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

    
58
# constants used to create InstancePolicy dictionary
59
TISPECS_GROUP_TYPES = {
60
  constants.ISPECS_MIN: constants.VTYPE_INT,
61
  constants.ISPECS_MAX: constants.VTYPE_INT,
62
  }
63

    
64
TISPECS_CLUSTER_TYPES = {
65
  constants.ISPECS_MIN: constants.VTYPE_INT,
66
  constants.ISPECS_MAX: constants.VTYPE_INT,
67
  constants.ISPECS_STD: constants.VTYPE_INT,
68
  }
69

    
70

    
71
def FillDict(defaults_dict, custom_dict, skip_keys=None):
72
  """Basic function to apply settings on top a default dict.
73

74
  @type defaults_dict: dict
75
  @param defaults_dict: dictionary holding the default values
76
  @type custom_dict: dict
77
  @param custom_dict: dictionary holding customized value
78
  @type skip_keys: list
79
  @param skip_keys: which keys not to fill
80
  @rtype: dict
81
  @return: dict with the 'full' values
82

83
  """
84
  ret_dict = copy.deepcopy(defaults_dict)
85
  ret_dict.update(custom_dict)
86
  if skip_keys:
87
    for k in skip_keys:
88
      try:
89
        del ret_dict[k]
90
      except KeyError:
91
        pass
92
  return ret_dict
93

    
94

    
95
def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None):
96
  """Fills an instance policy with defaults.
97

98
  """
99
  assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
100
  ret_dict = {}
101
  for key in constants.IPOLICY_PARAMETERS:
102
    ret_dict[key] = FillDict(default_ipolicy[key],
103
                             custom_ipolicy.get(key, {}),
104
                             skip_keys=skip_keys)
105
  # list items
106
  for key in [constants.ISPECS_DTS]:
107
    ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
108

    
109
  return ret_dict
110

    
111

    
112
def UpgradeGroupedParams(target, defaults):
113
  """Update all groups for the target parameter.
114

115
  @type target: dict of dicts
116
  @param target: {group: {parameter: value}}
117
  @type defaults: dict
118
  @param defaults: default parameter values
119

120
  """
121
  if target is None:
122
    target = {constants.PP_DEFAULT: defaults}
123
  else:
124
    for group in target:
125
      target[group] = FillDict(defaults, target[group])
126
  return target
127

    
128

    
129
def UpgradeBeParams(target):
130
  """Update the be parameters dict to the new format.
131

132
  @type target: dict
133
  @param target: "be" parameters dict
134

135
  """
136
  if constants.BE_MEMORY in target:
137
    memory = target[constants.BE_MEMORY]
138
    target[constants.BE_MAXMEM] = memory
139
    target[constants.BE_MINMEM] = memory
140
    del target[constants.BE_MEMORY]
141

    
142

    
143
def UpgradeDiskParams(diskparams):
144
  """Upgrade the disk parameters.
145

146
  @type diskparams: dict
147
  @param diskparams: disk parameters to upgrade
148
  @rtype: dict
149
  @return: the upgraded disk parameters dit
150

151
  """
152
  result = dict()
153
  if diskparams is None:
154
    result = constants.DISK_DT_DEFAULTS.copy()
155
  else:
156
    # Update the disk parameter values for each disk template.
157
    # The code iterates over constants.DISK_TEMPLATES because new templates
158
    # might have been added.
159
    for template in constants.DISK_TEMPLATES:
160
      if template not in diskparams:
161
        result[template] = constants.DISK_DT_DEFAULTS[template].copy()
162
      else:
163
        result[template] = FillDict(constants.DISK_DT_DEFAULTS[template],
164
                                    diskparams[template])
165

    
166
  return result
167

    
168

    
169
def MakeEmptyIPolicy():
170
  """Create empty IPolicy dictionary.
171

172
  """
173
  return dict([
174
    (constants.ISPECS_MIN, {}),
175
    (constants.ISPECS_MAX, {}),
176
    (constants.ISPECS_STD, {}),
177
    ])
178

    
179

    
180
def CreateIPolicyFromOpts(ispecs_mem_size=None,
181
                          ispecs_cpu_count=None,
182
                          ispecs_disk_count=None,
183
                          ispecs_disk_size=None,
184
                          ispecs_nic_count=None,
185
                          ispecs_disk_templates=None,
186
                          group_ipolicy=False,
187
                          allowed_values=None,
188
                          fill_all=False):
189
  """Creation of instance policy based on command line options.
190

191
  @param fill_all: whether for cluster policies we should ensure that
192
    all values are filled
193

194

195
  """
196
  # prepare ipolicy dict
197
  ipolicy_transposed = {
198
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
199
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
200
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
201
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
202
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
203
    }
204

    
205
  # first, check that the values given are correct
206
  if group_ipolicy:
207
    forced_type = TISPECS_GROUP_TYPES
208
  else:
209
    forced_type = TISPECS_CLUSTER_TYPES
210

    
211
  for specs in ipolicy_transposed.values():
212
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
213

    
214
  # then transpose
215
  ipolicy_out = MakeEmptyIPolicy()
216
  for name, specs in ipolicy_transposed.iteritems():
217
    assert name in constants.ISPECS_PARAMETERS
218
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
219
      ipolicy_out[key][name] = val
220

    
221
  # no filldict for lists
222
  if not group_ipolicy and fill_all and ispecs_disk_templates is None:
223
    ispecs_disk_templates = constants.DISK_TEMPLATES
224
  if ispecs_disk_templates is not None:
225
    ipolicy_out[constants.ISPECS_DTS] = list(ispecs_disk_templates)
226

    
227
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
228

    
229
  return ipolicy_out
230

    
231

    
232
class ConfigObject(object):
233
  """A generic config object.
234

235
  It has the following properties:
236

237
    - provides somewhat safe recursive unpickling and pickling for its classes
238
    - unset attributes which are defined in slots are always returned
239
      as None instead of raising an error
240

241
  Classes derived from this must always declare __slots__ (we use many
242
  config objects and the memory reduction is useful)
243

244
  """
245
  __slots__ = []
246

    
247
  def __init__(self, **kwargs):
248
    for k, v in kwargs.iteritems():
249
      setattr(self, k, v)
250

    
251
  def __getattr__(self, name):
252
    if name not in self._all_slots():
253
      raise AttributeError("Invalid object attribute %s.%s" %
254
                           (type(self).__name__, name))
255
    return None
256

    
257
  def __setstate__(self, state):
258
    slots = self._all_slots()
259
    for name in state:
260
      if name in slots:
261
        setattr(self, name, state[name])
262

    
263
  @classmethod
264
  def _all_slots(cls):
265
    """Compute the list of all declared slots for a class.
266

267
    """
268
    slots = []
269
    for parent in cls.__mro__:
270
      slots.extend(getattr(parent, "__slots__", []))
271
    return slots
272

    
273
  def ToDict(self):
274
    """Convert to a dict holding only standard python types.
275

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

282
    """
283
    result = {}
284
    for name in self._all_slots():
285
      value = getattr(self, name, None)
286
      if value is not None:
287
        result[name] = value
288
    return result
289

    
290
  __getstate__ = ToDict
291

    
292
  @classmethod
293
  def FromDict(cls, val):
294
    """Create an object from a dictionary.
295

296
    This generic routine takes a dict, instantiates a new instance of
297
    the given class, and sets attributes based on the dict content.
298

299
    As for `ToDict`, this does not work if the class has children
300
    who are ConfigObjects themselves (e.g. the nics list in an
301
    Instance), in which case the object should subclass the function
302
    and alter the objects.
303

304
    """
305
    if not isinstance(val, dict):
306
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
307
                                      " expected dict, got %s" % type(val))
308
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
309
    obj = cls(**val_str) # pylint: disable=W0142
310
    return obj
311

    
312
  @staticmethod
313
  def _ContainerToDicts(container):
314
    """Convert the elements of a container to standard python types.
315

316
    This method converts a container with elements derived from
317
    ConfigData to standard python types. If the container is a dict,
318
    we don't touch the keys, only the values.
319

320
    """
321
    if isinstance(container, dict):
322
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
323
    elif isinstance(container, (list, tuple, set, frozenset)):
324
      ret = [elem.ToDict() for elem in container]
325
    else:
326
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
327
                      type(container))
328
    return ret
329

    
330
  @staticmethod
331
  def _ContainerFromDicts(source, c_type, e_type):
332
    """Convert a container from standard python types.
333

334
    This method converts a container with standard python types to
335
    ConfigData objects. If the container is a dict, we don't touch the
336
    keys, only the values.
337

338
    """
339
    if not isinstance(c_type, type):
340
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
341
                      " not a type" % type(c_type))
342
    if source is None:
343
      source = c_type()
344
    if c_type is dict:
345
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
346
    elif c_type in (list, tuple, set, frozenset):
347
      ret = c_type([e_type.FromDict(elem) for elem in source])
348
    else:
349
      raise TypeError("Invalid container type %s passed to"
350
                      " _ContainerFromDicts" % c_type)
351
    return ret
352

    
353
  def Copy(self):
354
    """Makes a deep copy of the current object and its children.
355

356
    """
357
    dict_form = self.ToDict()
358
    clone_obj = self.__class__.FromDict(dict_form)
359
    return clone_obj
360

    
361
  def __repr__(self):
362
    """Implement __repr__ for ConfigObjects."""
363
    return repr(self.ToDict())
364

    
365
  def UpgradeConfig(self):
366
    """Fill defaults for missing configuration values.
367

368
    This method will be called at configuration load time, and its
369
    implementation will be object dependent.
370

371
    """
372
    pass
373

    
374

    
375
class TaggableObject(ConfigObject):
376
  """An generic class supporting tags.
377

378
  """
379
  __slots__ = ["tags"]
380
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
381

    
382
  @classmethod
383
  def ValidateTag(cls, tag):
384
    """Check if a tag is valid.
385

386
    If the tag is invalid, an errors.TagError will be raised. The
387
    function has no return value.
388

389
    """
390
    if not isinstance(tag, basestring):
391
      raise errors.TagError("Invalid tag type (not a string)")
392
    if len(tag) > constants.MAX_TAG_LEN:
393
      raise errors.TagError("Tag too long (>%d characters)" %
394
                            constants.MAX_TAG_LEN)
395
    if not tag:
396
      raise errors.TagError("Tags cannot be empty")
397
    if not cls.VALID_TAG_RE.match(tag):
398
      raise errors.TagError("Tag contains invalid characters")
399

    
400
  def GetTags(self):
401
    """Return the tags list.
402

403
    """
404
    tags = getattr(self, "tags", None)
405
    if tags is None:
406
      tags = self.tags = set()
407
    return tags
408

    
409
  def AddTag(self, tag):
410
    """Add a new tag.
411

412
    """
413
    self.ValidateTag(tag)
414
    tags = self.GetTags()
415
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
416
      raise errors.TagError("Too many tags")
417
    self.GetTags().add(tag)
418

    
419
  def RemoveTag(self, tag):
420
    """Remove a tag.
421

422
    """
423
    self.ValidateTag(tag)
424
    tags = self.GetTags()
425
    try:
426
      tags.remove(tag)
427
    except KeyError:
428
      raise errors.TagError("Tag not found")
429

    
430
  def ToDict(self):
431
    """Taggable-object-specific conversion to standard python types.
432

433
    This replaces the tags set with a list.
434

435
    """
436
    bo = super(TaggableObject, self).ToDict()
437

    
438
    tags = bo.get("tags", None)
439
    if isinstance(tags, set):
440
      bo["tags"] = list(tags)
441
    return bo
442

    
443
  @classmethod
444
  def FromDict(cls, val):
445
    """Custom function for instances.
446

447
    """
448
    obj = super(TaggableObject, cls).FromDict(val)
449
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
450
      obj.tags = set(obj.tags)
451
    return obj
452

    
453

    
454
class MasterNetworkParameters(ConfigObject):
455
  """Network configuration parameters for the master
456

457
  @ivar name: master name
458
  @ivar ip: master IP
459
  @ivar netmask: master netmask
460
  @ivar netdev: master network device
461
  @ivar ip_family: master IP family
462

463
  """
464
  __slots__ = [
465
    "name",
466
    "ip",
467
    "netmask",
468
    "netdev",
469
    "ip_family"
470
    ]
471

    
472

    
473
class ConfigData(ConfigObject):
474
  """Top-level config object."""
475
  __slots__ = [
476
    "version",
477
    "cluster",
478
    "nodes",
479
    "nodegroups",
480
    "instances",
481
    "serial_no",
482
    ] + _TIMESTAMPS
483

    
484
  def ToDict(self):
485
    """Custom function for top-level config data.
486

487
    This just replaces the list of instances, nodes and the cluster
488
    with standard python types.
489

490
    """
491
    mydict = super(ConfigData, self).ToDict()
492
    mydict["cluster"] = mydict["cluster"].ToDict()
493
    for key in "nodes", "instances", "nodegroups":
494
      mydict[key] = self._ContainerToDicts(mydict[key])
495

    
496
    return mydict
497

    
498
  @classmethod
499
  def FromDict(cls, val):
500
    """Custom function for top-level config data
501

502
    """
503
    obj = super(ConfigData, cls).FromDict(val)
504
    obj.cluster = Cluster.FromDict(obj.cluster)
505
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
506
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
507
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
508
    return obj
509

    
510
  def HasAnyDiskOfType(self, dev_type):
511
    """Check if in there is at disk of the given type in the configuration.
512

513
    @type dev_type: L{constants.LDS_BLOCK}
514
    @param dev_type: the type to look for
515
    @rtype: boolean
516
    @return: boolean indicating if a disk of the given type was found or not
517

518
    """
519
    for instance in self.instances.values():
520
      for disk in instance.disks:
521
        if disk.IsBasedOnDiskType(dev_type):
522
          return True
523
    return False
524

    
525
  def UpgradeConfig(self):
526
    """Fill defaults for missing configuration values.
527

528
    """
529
    self.cluster.UpgradeConfig()
530
    for node in self.nodes.values():
531
      node.UpgradeConfig()
532
    for instance in self.instances.values():
533
      instance.UpgradeConfig()
534
    if self.nodegroups is None:
535
      self.nodegroups = {}
536
    for nodegroup in self.nodegroups.values():
537
      nodegroup.UpgradeConfig()
538
    if self.cluster.drbd_usermode_helper is None:
539
      # To decide if we set an helper let's check if at least one instance has
540
      # a DRBD disk. This does not cover all the possible scenarios but it
541
      # gives a good approximation.
542
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
543
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
544

    
545

    
546
class NIC(ConfigObject):
547
  """Config object representing a network card."""
548
  __slots__ = ["mac", "ip", "nicparams"]
549

    
550
  @classmethod
551
  def CheckParameterSyntax(cls, nicparams):
552
    """Check the given parameters for validity.
553

554
    @type nicparams:  dict
555
    @param nicparams: dictionary with parameter names/value
556
    @raise errors.ConfigurationError: when a parameter is not valid
557

558
    """
559
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
560
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
561
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
562
      raise errors.ConfigurationError(err)
563

    
564
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
565
        not nicparams[constants.NIC_LINK]):
566
      err = "Missing bridged nic link"
567
      raise errors.ConfigurationError(err)
568

    
569

    
570
class Disk(ConfigObject):
571
  """Config object representing a block device."""
572
  __slots__ = ["dev_type", "logical_id", "physical_id",
573
               "children", "iv_name", "size", "mode", "params"]
574

    
575
  def CreateOnSecondary(self):
576
    """Test if this device needs to be created on a secondary node."""
577
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
578

    
579
  def AssembleOnSecondary(self):
580
    """Test if this device needs to be assembled on a secondary node."""
581
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
582

    
583
  def OpenOnSecondary(self):
584
    """Test if this device needs to be opened on a secondary node."""
585
    return self.dev_type in (constants.LD_LV,)
586

    
587
  def StaticDevPath(self):
588
    """Return the device path if this device type has a static one.
589

590
    Some devices (LVM for example) live always at the same /dev/ path,
591
    irrespective of their status. For such devices, we return this
592
    path, for others we return None.
593

594
    @warning: The path returned is not a normalized pathname; callers
595
        should check that it is a valid path.
596

597
    """
598
    if self.dev_type == constants.LD_LV:
599
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
600
    elif self.dev_type == constants.LD_BLOCKDEV:
601
      return self.logical_id[1]
602
    return None
603

    
604
  def ChildrenNeeded(self):
605
    """Compute the needed number of children for activation.
606

607
    This method will return either -1 (all children) or a positive
608
    number denoting the minimum number of children needed for
609
    activation (only mirrored devices will usually return >=0).
610

611
    Currently, only DRBD8 supports diskless activation (therefore we
612
    return 0), for all other we keep the previous semantics and return
613
    -1.
614

615
    """
616
    if self.dev_type == constants.LD_DRBD8:
617
      return 0
618
    return -1
619

    
620
  def IsBasedOnDiskType(self, dev_type):
621
    """Check if the disk or its children are based on the given type.
622

623
    @type dev_type: L{constants.LDS_BLOCK}
624
    @param dev_type: the type to look for
625
    @rtype: boolean
626
    @return: boolean indicating if a device of the given type was found or not
627

628
    """
629
    if self.children:
630
      for child in self.children:
631
        if child.IsBasedOnDiskType(dev_type):
632
          return True
633
    return self.dev_type == dev_type
634

    
635
  def GetNodes(self, node):
636
    """This function returns the nodes this device lives on.
637

638
    Given the node on which the parent of the device lives on (or, in
639
    case of a top-level device, the primary node of the devices'
640
    instance), this function will return a list of nodes on which this
641
    devices needs to (or can) be assembled.
642

643
    """
644
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
645
                         constants.LD_BLOCKDEV]:
646
      result = [node]
647
    elif self.dev_type in constants.LDS_DRBD:
648
      result = [self.logical_id[0], self.logical_id[1]]
649
      if node not in result:
650
        raise errors.ConfigurationError("DRBD device passed unknown node")
651
    else:
652
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
653
    return result
654

    
655
  def ComputeNodeTree(self, parent_node):
656
    """Compute the node/disk tree for this disk and its children.
657

658
    This method, given the node on which the parent disk lives, will
659
    return the list of all (node, disk) pairs which describe the disk
660
    tree in the most compact way. For example, a drbd/lvm stack
661
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
662
    which represents all the top-level devices on the nodes.
663

664
    """
665
    my_nodes = self.GetNodes(parent_node)
666
    result = [(node, self) for node in my_nodes]
667
    if not self.children:
668
      # leaf device
669
      return result
670
    for node in my_nodes:
671
      for child in self.children:
672
        child_result = child.ComputeNodeTree(node)
673
        if len(child_result) == 1:
674
          # child (and all its descendants) is simple, doesn't split
675
          # over multiple hosts, so we don't need to describe it, our
676
          # own entry for this node describes it completely
677
          continue
678
        else:
679
          # check if child nodes differ from my nodes; note that
680
          # subdisk can differ from the child itself, and be instead
681
          # one of its descendants
682
          for subnode, subdisk in child_result:
683
            if subnode not in my_nodes:
684
              result.append((subnode, subdisk))
685
            # otherwise child is under our own node, so we ignore this
686
            # entry (but probably the other results in the list will
687
            # be different)
688
    return result
689

    
690
  def ComputeGrowth(self, amount):
691
    """Compute the per-VG growth requirements.
692

693
    This only works for VG-based disks.
694

695
    @type amount: integer
696
    @param amount: the desired increase in (user-visible) disk space
697
    @rtype: dict
698
    @return: a dictionary of volume-groups and the required size
699

700
    """
701
    if self.dev_type == constants.LD_LV:
702
      return {self.logical_id[0]: amount}
703
    elif self.dev_type == constants.LD_DRBD8:
704
      if self.children:
705
        return self.children[0].ComputeGrowth(amount)
706
      else:
707
        return {}
708
    else:
709
      # Other disk types do not require VG space
710
      return {}
711

    
712
  def RecordGrow(self, amount):
713
    """Update the size of this disk after growth.
714

715
    This method recurses over the disks's children and updates their
716
    size correspondigly. The method needs to be kept in sync with the
717
    actual algorithms from bdev.
718

719
    """
720
    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
721
      self.size += amount
722
    elif self.dev_type == constants.LD_DRBD8:
723
      if self.children:
724
        self.children[0].RecordGrow(amount)
725
      self.size += amount
726
    else:
727
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
728
                                   " disk type %s" % self.dev_type)
729

    
730
  def UnsetSize(self):
731
    """Sets recursively the size to zero for the disk and its children.
732

733
    """
734
    if self.children:
735
      for child in self.children:
736
        child.UnsetSize()
737
    self.size = 0
738

    
739
  def SetPhysicalID(self, target_node, nodes_ip):
740
    """Convert the logical ID to the physical ID.
741

742
    This is used only for drbd, which needs ip/port configuration.
743

744
    The routine descends down and updates its children also, because
745
    this helps when the only the top device is passed to the remote
746
    node.
747

748
    Arguments:
749
      - target_node: the node we wish to configure for
750
      - nodes_ip: a mapping of node name to ip
751

752
    The target_node must exist in in nodes_ip, and must be one of the
753
    nodes in the logical ID for each of the DRBD devices encountered
754
    in the disk tree.
755

756
    """
757
    if self.children:
758
      for child in self.children:
759
        child.SetPhysicalID(target_node, nodes_ip)
760

    
761
    if self.logical_id is None and self.physical_id is not None:
762
      return
763
    if self.dev_type in constants.LDS_DRBD:
764
      pnode, snode, port, pminor, sminor, secret = self.logical_id
765
      if target_node not in (pnode, snode):
766
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
767
                                        target_node)
768
      pnode_ip = nodes_ip.get(pnode, None)
769
      snode_ip = nodes_ip.get(snode, None)
770
      if pnode_ip is None or snode_ip is None:
771
        raise errors.ConfigurationError("Can't find primary or secondary node"
772
                                        " for %s" % str(self))
773
      p_data = (pnode_ip, port)
774
      s_data = (snode_ip, port)
775
      if pnode == target_node:
776
        self.physical_id = p_data + s_data + (pminor, secret)
777
      else: # it must be secondary, we tested above
778
        self.physical_id = s_data + p_data + (sminor, secret)
779
    else:
780
      self.physical_id = self.logical_id
781
    return
782

    
783
  def ToDict(self):
784
    """Disk-specific conversion to standard python types.
785

786
    This replaces the children lists of objects with lists of
787
    standard python types.
788

789
    """
790
    bo = super(Disk, self).ToDict()
791

    
792
    for attr in ("children",):
793
      alist = bo.get(attr, None)
794
      if alist:
795
        bo[attr] = self._ContainerToDicts(alist)
796
    return bo
797

    
798
  @classmethod
799
  def FromDict(cls, val):
800
    """Custom function for Disks
801

802
    """
803
    obj = super(Disk, cls).FromDict(val)
804
    if obj.children:
805
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
806
    if obj.logical_id and isinstance(obj.logical_id, list):
807
      obj.logical_id = tuple(obj.logical_id)
808
    if obj.physical_id and isinstance(obj.physical_id, list):
809
      obj.physical_id = tuple(obj.physical_id)
810
    if obj.dev_type in constants.LDS_DRBD:
811
      # we need a tuple of length six here
812
      if len(obj.logical_id) < 6:
813
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
814
    return obj
815

    
816
  def __str__(self):
817
    """Custom str() formatter for disks.
818

819
    """
820
    if self.dev_type == constants.LD_LV:
821
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
822
    elif self.dev_type in constants.LDS_DRBD:
823
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
824
      val = "<DRBD8("
825
      if self.physical_id is None:
826
        phy = "unconfigured"
827
      else:
828
        phy = ("configured as %s:%s %s:%s" %
829
               (self.physical_id[0], self.physical_id[1],
830
                self.physical_id[2], self.physical_id[3]))
831

    
832
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
833
              (node_a, minor_a, node_b, minor_b, port, phy))
834
      if self.children and self.children.count(None) == 0:
835
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
836
      else:
837
        val += "no local storage"
838
    else:
839
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
840
             (self.dev_type, self.logical_id, self.physical_id, self.children))
841
    if self.iv_name is None:
842
      val += ", not visible"
843
    else:
844
      val += ", visible as /dev/%s" % self.iv_name
845
    if isinstance(self.size, int):
846
      val += ", size=%dm)>" % self.size
847
    else:
848
      val += ", size='%s')>" % (self.size,)
849
    return val
850

    
851
  def Verify(self):
852
    """Checks that this disk is correctly configured.
853

854
    """
855
    all_errors = []
856
    if self.mode not in constants.DISK_ACCESS_SET:
857
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
858
    return all_errors
859

    
860
  def UpgradeConfig(self):
861
    """Fill defaults for missing configuration values.
862

863
    """
864
    if self.children:
865
      for child in self.children:
866
        child.UpgradeConfig()
867

    
868
    if not self.params:
869
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
870
    else:
871
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
872
                             self.params)
873
    # add here config upgrade for this disk
874

    
875

    
876
class InstancePolicy(ConfigObject):
877
  """Config object representing instance policy limits dictionary."""
878
  __slots__ = ["min", "max", "std", "disk_templates"]
879

    
880
  @classmethod
881
  def CheckParameterSyntax(cls, ipolicy):
882
    """ Check the instance policy for validity.
883

884
    """
885
    for param in constants.ISPECS_PARAMETERS:
886
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
887
    if constants.ISPECS_DTS in ipolicy:
888
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.ISPECS_DTS])
889
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
890
    if wrong_keys:
891
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
892
                                      utils.CommaJoin(wrong_keys))
893

    
894
  @classmethod
895
  def CheckISpecSyntax(cls, ipolicy, name):
896
    """Check the instance policy for validity on a given key.
897

898
    We check if the instance policy makes sense for a given key, that is
899
    if ipolicy[min][name] <= ipolicy[std][name] <= ipolicy[max][name].
900

901
    @type ipolicy: dict
902
    @param ipolicy: dictionary with min, max, std specs
903
    @type name: string
904
    @param name: what are the limits for
905
    @raise errors.ConfigureError: when specs for given name are not valid
906

907
    """
908
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
909
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
910
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
911
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
912
           (name,
913
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
914
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
915
            ipolicy[constants.ISPECS_STD].get(name, "-")))
916
    if min_v > std_v or std_v > max_v:
917
      raise errors.ConfigurationError(err)
918

    
919
  @classmethod
920
  def CheckDiskTemplates(cls, disk_templates):
921
    """Checks the disk templates for validity.
922

923
    """
924
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
925
    if wrong:
926
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
927
                                      utils.CommaJoin(wrong))
928

    
929

    
930
class Instance(TaggableObject):
931
  """Config object representing an instance."""
932
  __slots__ = [
933
    "name",
934
    "primary_node",
935
    "os",
936
    "hypervisor",
937
    "hvparams",
938
    "beparams",
939
    "osparams",
940
    "admin_state",
941
    "nics",
942
    "disks",
943
    "disk_template",
944
    "network_port",
945
    "serial_no",
946
    ] + _TIMESTAMPS + _UUID
947

    
948
  def _ComputeSecondaryNodes(self):
949
    """Compute the list of secondary nodes.
950

951
    This is a simple wrapper over _ComputeAllNodes.
952

953
    """
954
    all_nodes = set(self._ComputeAllNodes())
955
    all_nodes.discard(self.primary_node)
956
    return tuple(all_nodes)
957

    
958
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
959
                             "List of secondary nodes")
960

    
961
  def _ComputeAllNodes(self):
962
    """Compute the list of all nodes.
963

964
    Since the data is already there (in the drbd disks), keeping it as
965
    a separate normal attribute is redundant and if not properly
966
    synchronised can cause problems. Thus it's better to compute it
967
    dynamically.
968

969
    """
970
    def _Helper(nodes, device):
971
      """Recursively computes nodes given a top device."""
972
      if device.dev_type in constants.LDS_DRBD:
973
        nodea, nodeb = device.logical_id[:2]
974
        nodes.add(nodea)
975
        nodes.add(nodeb)
976
      if device.children:
977
        for child in device.children:
978
          _Helper(nodes, child)
979

    
980
    all_nodes = set()
981
    all_nodes.add(self.primary_node)
982
    for device in self.disks:
983
      _Helper(all_nodes, device)
984
    return tuple(all_nodes)
985

    
986
  all_nodes = property(_ComputeAllNodes, None, None,
987
                       "List of all nodes of the instance")
988

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

992
    This function figures out what logical volumes should belong on
993
    which nodes, recursing through a device tree.
994

995
    @param lvmap: optional dictionary to receive the
996
        'node' : ['lv', ...] data.
997

998
    @return: None if lvmap arg is given, otherwise, a dictionary of
999
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1000
        volumeN is of the form "vg_name/lv_name", compatible with
1001
        GetVolumeList()
1002

1003
    """
1004
    if node == None:
1005
      node = self.primary_node
1006

    
1007
    if lvmap is None:
1008
      lvmap = {
1009
        node: [],
1010
        }
1011
      ret = lvmap
1012
    else:
1013
      if not node in lvmap:
1014
        lvmap[node] = []
1015
      ret = None
1016

    
1017
    if not devs:
1018
      devs = self.disks
1019

    
1020
    for dev in devs:
1021
      if dev.dev_type == constants.LD_LV:
1022
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1023

    
1024
      elif dev.dev_type in constants.LDS_DRBD:
1025
        if dev.children:
1026
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1027
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1028

    
1029
      elif dev.children:
1030
        self.MapLVsByNode(lvmap, dev.children, node)
1031

    
1032
    return ret
1033

    
1034
  def FindDisk(self, idx):
1035
    """Find a disk given having a specified index.
1036

1037
    This is just a wrapper that does validation of the index.
1038

1039
    @type idx: int
1040
    @param idx: the disk index
1041
    @rtype: L{Disk}
1042
    @return: the corresponding disk
1043
    @raise errors.OpPrereqError: when the given index is not valid
1044

1045
    """
1046
    try:
1047
      idx = int(idx)
1048
      return self.disks[idx]
1049
    except (TypeError, ValueError), err:
1050
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1051
                                 errors.ECODE_INVAL)
1052
    except IndexError:
1053
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1054
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1055
                                 errors.ECODE_INVAL)
1056

    
1057
  def ToDict(self):
1058
    """Instance-specific conversion to standard python types.
1059

1060
    This replaces the children lists of objects with lists of standard
1061
    python types.
1062

1063
    """
1064
    bo = super(Instance, self).ToDict()
1065

    
1066
    for attr in "nics", "disks":
1067
      alist = bo.get(attr, None)
1068
      if alist:
1069
        nlist = self._ContainerToDicts(alist)
1070
      else:
1071
        nlist = []
1072
      bo[attr] = nlist
1073
    return bo
1074

    
1075
  @classmethod
1076
  def FromDict(cls, val):
1077
    """Custom function for instances.
1078

1079
    """
1080
    if "admin_state" not in val:
1081
      if val.get("admin_up", False):
1082
        val["admin_state"] = constants.ADMINST_UP
1083
      else:
1084
        val["admin_state"] = constants.ADMINST_DOWN
1085
    if "admin_up" in val:
1086
      del val["admin_up"]
1087
    obj = super(Instance, cls).FromDict(val)
1088
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1089
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1090
    return obj
1091

    
1092
  def UpgradeConfig(self):
1093
    """Fill defaults for missing configuration values.
1094

1095
    """
1096
    for nic in self.nics:
1097
      nic.UpgradeConfig()
1098
    for disk in self.disks:
1099
      disk.UpgradeConfig()
1100
    if self.hvparams:
1101
      for key in constants.HVC_GLOBALS:
1102
        try:
1103
          del self.hvparams[key]
1104
        except KeyError:
1105
          pass
1106
    if self.osparams is None:
1107
      self.osparams = {}
1108
    UpgradeBeParams(self.beparams)
1109

    
1110

    
1111
class OS(ConfigObject):
1112
  """Config object representing an operating system.
1113

1114
  @type supported_parameters: list
1115
  @ivar supported_parameters: a list of tuples, name and description,
1116
      containing the supported parameters by this OS
1117

1118
  @type VARIANT_DELIM: string
1119
  @cvar VARIANT_DELIM: the variant delimiter
1120

1121
  """
1122
  __slots__ = [
1123
    "name",
1124
    "path",
1125
    "api_versions",
1126
    "create_script",
1127
    "export_script",
1128
    "import_script",
1129
    "rename_script",
1130
    "verify_script",
1131
    "supported_variants",
1132
    "supported_parameters",
1133
    ]
1134

    
1135
  VARIANT_DELIM = "+"
1136

    
1137
  @classmethod
1138
  def SplitNameVariant(cls, name):
1139
    """Splits the name into the proper name and variant.
1140

1141
    @param name: the OS (unprocessed) name
1142
    @rtype: list
1143
    @return: a list of two elements; if the original name didn't
1144
        contain a variant, it's returned as an empty string
1145

1146
    """
1147
    nv = name.split(cls.VARIANT_DELIM, 1)
1148
    if len(nv) == 1:
1149
      nv.append("")
1150
    return nv
1151

    
1152
  @classmethod
1153
  def GetName(cls, name):
1154
    """Returns the proper name of the os (without the variant).
1155

1156
    @param name: the OS (unprocessed) name
1157

1158
    """
1159
    return cls.SplitNameVariant(name)[0]
1160

    
1161
  @classmethod
1162
  def GetVariant(cls, name):
1163
    """Returns the variant the os (without the base name).
1164

1165
    @param name: the OS (unprocessed) name
1166

1167
    """
1168
    return cls.SplitNameVariant(name)[1]
1169

    
1170

    
1171
class NodeHvState(ConfigObject):
1172
  """Hypvervisor state on a node.
1173

1174
  @ivar mem_total: Total amount of memory
1175
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1176
    available)
1177
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1178
    rounding
1179
  @ivar mem_inst: Memory used by instances living on node
1180
  @ivar cpu_total: Total node CPU core count
1181
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1182

1183
  """
1184
  __slots__ = [
1185
    "mem_total",
1186
    "mem_node",
1187
    "mem_hv",
1188
    "mem_inst",
1189
    "cpu_total",
1190
    "cpu_node",
1191
    ] + _TIMESTAMPS
1192

    
1193

    
1194
class NodeDiskState(ConfigObject):
1195
  """Disk state on a node.
1196

1197
  """
1198
  __slots__ = [
1199
    "total",
1200
    "reserved",
1201
    "overhead",
1202
    ] + _TIMESTAMPS
1203

    
1204

    
1205
class Node(TaggableObject):
1206
  """Config object representing a node.
1207

1208
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1209
  @ivar hv_state_static: Hypervisor state overriden by user
1210
  @ivar disk_state: Disk state (e.g. free space)
1211
  @ivar disk_state_static: Disk state overriden by user
1212

1213
  """
1214
  __slots__ = [
1215
    "name",
1216
    "primary_ip",
1217
    "secondary_ip",
1218
    "serial_no",
1219
    "master_candidate",
1220
    "offline",
1221
    "drained",
1222
    "group",
1223
    "master_capable",
1224
    "vm_capable",
1225
    "ndparams",
1226
    "powered",
1227
    "hv_state",
1228
    "hv_state_static",
1229
    "disk_state",
1230
    "disk_state_static",
1231
    ] + _TIMESTAMPS + _UUID
1232

    
1233
  def UpgradeConfig(self):
1234
    """Fill defaults for missing configuration values.
1235

1236
    """
1237
    # pylint: disable=E0203
1238
    # because these are "defined" via slots, not manually
1239
    if self.master_capable is None:
1240
      self.master_capable = True
1241

    
1242
    if self.vm_capable is None:
1243
      self.vm_capable = True
1244

    
1245
    if self.ndparams is None:
1246
      self.ndparams = {}
1247

    
1248
    if self.powered is None:
1249
      self.powered = True
1250

    
1251
  def ToDict(self):
1252
    """Custom function for serializing.
1253

1254
    """
1255
    data = super(Node, self).ToDict()
1256

    
1257
    hv_state = data.get("hv_state", None)
1258
    if hv_state is not None:
1259
      data["hv_state"] = self._ContainerToDicts(hv_state)
1260

    
1261
    disk_state = data.get("disk_state", None)
1262
    if disk_state is not None:
1263
      data["disk_state"] = \
1264
        dict((key, self._ContainerToDicts(value))
1265
             for (key, value) in disk_state.items())
1266

    
1267
    return data
1268

    
1269
  @classmethod
1270
  def FromDict(cls, val):
1271
    """Custom function for deserializing.
1272

1273
    """
1274
    obj = super(Node, cls).FromDict(val)
1275

    
1276
    if obj.hv_state is not None:
1277
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1278

    
1279
    if obj.disk_state is not None:
1280
      obj.disk_state = \
1281
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1282
             for (key, value) in obj.disk_state.items())
1283

    
1284
    return obj
1285

    
1286

    
1287
class NodeGroup(TaggableObject):
1288
  """Config object representing a node group."""
1289
  __slots__ = [
1290
    "name",
1291
    "members",
1292
    "ndparams",
1293
    "diskparams",
1294
    "ipolicy",
1295
    "serial_no",
1296
    "hv_state_static",
1297
    "disk_state_static",
1298
    "alloc_policy",
1299
    ] + _TIMESTAMPS + _UUID
1300

    
1301
  def ToDict(self):
1302
    """Custom function for nodegroup.
1303

1304
    This discards the members object, which gets recalculated and is only kept
1305
    in memory.
1306

1307
    """
1308
    mydict = super(NodeGroup, self).ToDict()
1309
    del mydict["members"]
1310
    return mydict
1311

    
1312
  @classmethod
1313
  def FromDict(cls, val):
1314
    """Custom function for nodegroup.
1315

1316
    The members slot is initialized to an empty list, upon deserialization.
1317

1318
    """
1319
    obj = super(NodeGroup, cls).FromDict(val)
1320
    obj.members = []
1321
    return obj
1322

    
1323
  def UpgradeConfig(self):
1324
    """Fill defaults for missing configuration values.
1325

1326
    """
1327
    if self.ndparams is None:
1328
      self.ndparams = {}
1329

    
1330
    if self.serial_no is None:
1331
      self.serial_no = 1
1332

    
1333
    if self.alloc_policy is None:
1334
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1335

    
1336
    # We only update mtime, and not ctime, since we would not be able
1337
    # to provide a correct value for creation time.
1338
    if self.mtime is None:
1339
      self.mtime = time.time()
1340

    
1341
    self.diskparams = UpgradeDiskParams(self.diskparams)
1342
    if self.ipolicy is None:
1343
      self.ipolicy = MakeEmptyIPolicy()
1344

    
1345
  def FillND(self, node):
1346
    """Return filled out ndparams for L{objects.Node}
1347

1348
    @type node: L{objects.Node}
1349
    @param node: A Node object to fill
1350
    @return a copy of the node's ndparams with defaults filled
1351

1352
    """
1353
    return self.SimpleFillND(node.ndparams)
1354

    
1355
  def SimpleFillND(self, ndparams):
1356
    """Fill a given ndparams dict with defaults.
1357

1358
    @type ndparams: dict
1359
    @param ndparams: the dict to fill
1360
    @rtype: dict
1361
    @return: a copy of the passed in ndparams with missing keys filled
1362
        from the node group defaults
1363

1364
    """
1365
    return FillDict(self.ndparams, ndparams)
1366

    
1367

    
1368
class Cluster(TaggableObject):
1369
  """Config object representing the cluster."""
1370
  __slots__ = [
1371
    "serial_no",
1372
    "rsahostkeypub",
1373
    "highest_used_port",
1374
    "tcpudp_port_pool",
1375
    "mac_prefix",
1376
    "volume_group_name",
1377
    "reserved_lvs",
1378
    "drbd_usermode_helper",
1379
    "default_bridge",
1380
    "default_hypervisor",
1381
    "master_node",
1382
    "master_ip",
1383
    "master_netdev",
1384
    "master_netmask",
1385
    "use_external_mip_script",
1386
    "cluster_name",
1387
    "file_storage_dir",
1388
    "shared_file_storage_dir",
1389
    "enabled_hypervisors",
1390
    "hvparams",
1391
    "ipolicy",
1392
    "os_hvp",
1393
    "beparams",
1394
    "osparams",
1395
    "nicparams",
1396
    "ndparams",
1397
    "diskparams",
1398
    "candidate_pool_size",
1399
    "modify_etc_hosts",
1400
    "modify_ssh_setup",
1401
    "maintain_node_health",
1402
    "uid_pool",
1403
    "default_iallocator",
1404
    "hidden_os",
1405
    "blacklisted_os",
1406
    "primary_ip_family",
1407
    "prealloc_wipe_disks",
1408
    "hv_state_static",
1409
    "disk_state_static",
1410
    ] + _TIMESTAMPS + _UUID
1411

    
1412
  def UpgradeConfig(self):
1413
    """Fill defaults for missing configuration values.
1414

1415
    """
1416
    # pylint: disable=E0203
1417
    # because these are "defined" via slots, not manually
1418
    if self.hvparams is None:
1419
      self.hvparams = constants.HVC_DEFAULTS
1420
    else:
1421
      for hypervisor in self.hvparams:
1422
        self.hvparams[hypervisor] = FillDict(
1423
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1424

    
1425
    if self.os_hvp is None:
1426
      self.os_hvp = {}
1427

    
1428
    # osparams added before 2.2
1429
    if self.osparams is None:
1430
      self.osparams = {}
1431

    
1432
    if self.ndparams is None:
1433
      self.ndparams = constants.NDC_DEFAULTS
1434

    
1435
    self.beparams = UpgradeGroupedParams(self.beparams,
1436
                                         constants.BEC_DEFAULTS)
1437
    for beparams_group in self.beparams:
1438
      UpgradeBeParams(self.beparams[beparams_group])
1439

    
1440
    migrate_default_bridge = not self.nicparams
1441
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1442
                                          constants.NICC_DEFAULTS)
1443
    if migrate_default_bridge:
1444
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1445
        self.default_bridge
1446

    
1447
    if self.modify_etc_hosts is None:
1448
      self.modify_etc_hosts = True
1449

    
1450
    if self.modify_ssh_setup is None:
1451
      self.modify_ssh_setup = True
1452

    
1453
    # default_bridge is no longer used in 2.1. The slot is left there to
1454
    # support auto-upgrading. It can be removed once we decide to deprecate
1455
    # upgrading straight from 2.0.
1456
    if self.default_bridge is not None:
1457
      self.default_bridge = None
1458

    
1459
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1460
    # code can be removed once upgrading straight from 2.0 is deprecated.
1461
    if self.default_hypervisor is not None:
1462
      self.enabled_hypervisors = ([self.default_hypervisor] +
1463
        [hvname for hvname in self.enabled_hypervisors
1464
         if hvname != self.default_hypervisor])
1465
      self.default_hypervisor = None
1466

    
1467
    # maintain_node_health added after 2.1.1
1468
    if self.maintain_node_health is None:
1469
      self.maintain_node_health = False
1470

    
1471
    if self.uid_pool is None:
1472
      self.uid_pool = []
1473

    
1474
    if self.default_iallocator is None:
1475
      self.default_iallocator = ""
1476

    
1477
    # reserved_lvs added before 2.2
1478
    if self.reserved_lvs is None:
1479
      self.reserved_lvs = []
1480

    
1481
    # hidden and blacklisted operating systems added before 2.2.1
1482
    if self.hidden_os is None:
1483
      self.hidden_os = []
1484

    
1485
    if self.blacklisted_os is None:
1486
      self.blacklisted_os = []
1487

    
1488
    # primary_ip_family added before 2.3
1489
    if self.primary_ip_family is None:
1490
      self.primary_ip_family = AF_INET
1491

    
1492
    if self.master_netmask is None:
1493
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1494
      self.master_netmask = ipcls.iplen
1495

    
1496
    if self.prealloc_wipe_disks is None:
1497
      self.prealloc_wipe_disks = False
1498

    
1499
    # shared_file_storage_dir added before 2.5
1500
    if self.shared_file_storage_dir is None:
1501
      self.shared_file_storage_dir = ""
1502

    
1503
    if self.use_external_mip_script is None:
1504
      self.use_external_mip_script = False
1505

    
1506
    self.diskparams = UpgradeDiskParams(self.diskparams)
1507

    
1508
    # instance policy added before 2.6
1509
    if self.ipolicy is None:
1510
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1511

    
1512
  @property
1513
  def primary_hypervisor(self):
1514
    """The first hypervisor is the primary.
1515

1516
    Useful, for example, for L{Node}'s hv/disk state.
1517

1518
    """
1519
    return self.enabled_hypervisors[0]
1520

    
1521
  def ToDict(self):
1522
    """Custom function for cluster.
1523

1524
    """
1525
    mydict = super(Cluster, self).ToDict()
1526
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1527
    return mydict
1528

    
1529
  @classmethod
1530
  def FromDict(cls, val):
1531
    """Custom function for cluster.
1532

1533
    """
1534
    obj = super(Cluster, cls).FromDict(val)
1535
    if not isinstance(obj.tcpudp_port_pool, set):
1536
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1537
    return obj
1538

    
1539
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1540
    """Get the default hypervisor parameters for the cluster.
1541

1542
    @param hypervisor: the hypervisor name
1543
    @param os_name: if specified, we'll also update the defaults for this OS
1544
    @param skip_keys: if passed, list of keys not to use
1545
    @return: the defaults dict
1546

1547
    """
1548
    if skip_keys is None:
1549
      skip_keys = []
1550

    
1551
    fill_stack = [self.hvparams.get(hypervisor, {})]
1552
    if os_name is not None:
1553
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1554
      fill_stack.append(os_hvp)
1555

    
1556
    ret_dict = {}
1557
    for o_dict in fill_stack:
1558
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1559

    
1560
    return ret_dict
1561

    
1562
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1563
    """Fill a given hvparams dict with cluster defaults.
1564

1565
    @type hv_name: string
1566
    @param hv_name: the hypervisor to use
1567
    @type os_name: string
1568
    @param os_name: the OS to use for overriding the hypervisor defaults
1569
    @type skip_globals: boolean
1570
    @param skip_globals: if True, the global hypervisor parameters will
1571
        not be filled
1572
    @rtype: dict
1573
    @return: a copy of the given hvparams with missing keys filled from
1574
        the cluster defaults
1575

1576
    """
1577
    if skip_globals:
1578
      skip_keys = constants.HVC_GLOBALS
1579
    else:
1580
      skip_keys = []
1581

    
1582
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1583
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1584

    
1585
  def FillHV(self, instance, skip_globals=False):
1586
    """Fill an instance's hvparams dict with cluster defaults.
1587

1588
    @type instance: L{objects.Instance}
1589
    @param instance: the instance parameter to fill
1590
    @type skip_globals: boolean
1591
    @param skip_globals: if True, the global hypervisor parameters will
1592
        not be filled
1593
    @rtype: dict
1594
    @return: a copy of the instance's hvparams with missing keys filled from
1595
        the cluster defaults
1596

1597
    """
1598
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1599
                             instance.hvparams, skip_globals)
1600

    
1601
  def SimpleFillBE(self, beparams):
1602
    """Fill a given beparams dict with cluster defaults.
1603

1604
    @type beparams: dict
1605
    @param beparams: the dict to fill
1606
    @rtype: dict
1607
    @return: a copy of the passed in beparams with missing keys filled
1608
        from the cluster defaults
1609

1610
    """
1611
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1612

    
1613
  def FillBE(self, instance):
1614
    """Fill an instance's beparams dict with cluster defaults.
1615

1616
    @type instance: L{objects.Instance}
1617
    @param instance: the instance parameter to fill
1618
    @rtype: dict
1619
    @return: a copy of the instance's beparams with missing keys filled from
1620
        the cluster defaults
1621

1622
    """
1623
    return self.SimpleFillBE(instance.beparams)
1624

    
1625
  def SimpleFillNIC(self, nicparams):
1626
    """Fill a given nicparams dict with cluster defaults.
1627

1628
    @type nicparams: dict
1629
    @param nicparams: the dict to fill
1630
    @rtype: dict
1631
    @return: a copy of the passed in nicparams with missing keys filled
1632
        from the cluster defaults
1633

1634
    """
1635
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1636

    
1637
  def SimpleFillOS(self, os_name, os_params):
1638
    """Fill an instance's osparams dict with cluster defaults.
1639

1640
    @type os_name: string
1641
    @param os_name: the OS name to use
1642
    @type os_params: dict
1643
    @param os_params: the dict to fill with default values
1644
    @rtype: dict
1645
    @return: a copy of the instance's osparams with missing keys filled from
1646
        the cluster defaults
1647

1648
    """
1649
    name_only = os_name.split("+", 1)[0]
1650
    # base OS
1651
    result = self.osparams.get(name_only, {})
1652
    # OS with variant
1653
    result = FillDict(result, self.osparams.get(os_name, {}))
1654
    # specified params
1655
    return FillDict(result, os_params)
1656

    
1657
  @staticmethod
1658
  def SimpleFillHvState(hv_state):
1659
    """Fill an hv_state sub dict with cluster defaults.
1660

1661
    """
1662
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1663

    
1664
  @staticmethod
1665
  def SimpleFillDiskState(disk_state):
1666
    """Fill an disk_state sub dict with cluster defaults.
1667

1668
    """
1669
    return FillDict(constants.DS_DEFAULTS, disk_state)
1670

    
1671
  def FillND(self, node, nodegroup):
1672
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1673

1674
    @type node: L{objects.Node}
1675
    @param node: A Node object to fill
1676
    @type nodegroup: L{objects.NodeGroup}
1677
    @param nodegroup: A Node object to fill
1678
    @return a copy of the node's ndparams with defaults filled
1679

1680
    """
1681
    return self.SimpleFillND(nodegroup.FillND(node))
1682

    
1683
  def SimpleFillND(self, ndparams):
1684
    """Fill a given ndparams dict with defaults.
1685

1686
    @type ndparams: dict
1687
    @param ndparams: the dict to fill
1688
    @rtype: dict
1689
    @return: a copy of the passed in ndparams with missing keys filled
1690
        from the cluster defaults
1691

1692
    """
1693
    return FillDict(self.ndparams, ndparams)
1694

    
1695
  def SimpleFillIPolicy(self, ipolicy):
1696
    """ Fill instance policy dict with defaults.
1697

1698
    @type ipolicy: dict
1699
    @param ipolicy: the dict to fill
1700
    @rtype: dict
1701
    @return: a copy of passed ipolicy with missing keys filled from
1702
      the cluster defaults
1703

1704
    """
1705
    return FillIPolicy(self.ipolicy, ipolicy)
1706

    
1707

    
1708
class BlockDevStatus(ConfigObject):
1709
  """Config object representing the status of a block device."""
1710
  __slots__ = [
1711
    "dev_path",
1712
    "major",
1713
    "minor",
1714
    "sync_percent",
1715
    "estimated_time",
1716
    "is_degraded",
1717
    "ldisk_status",
1718
    ]
1719

    
1720

    
1721
class ImportExportStatus(ConfigObject):
1722
  """Config object representing the status of an import or export."""
1723
  __slots__ = [
1724
    "recent_output",
1725
    "listen_port",
1726
    "connected",
1727
    "progress_mbytes",
1728
    "progress_throughput",
1729
    "progress_eta",
1730
    "progress_percent",
1731
    "exit_status",
1732
    "error_message",
1733
    ] + _TIMESTAMPS
1734

    
1735

    
1736
class ImportExportOptions(ConfigObject):
1737
  """Options for import/export daemon
1738

1739
  @ivar key_name: X509 key name (None for cluster certificate)
1740
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1741
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1742
  @ivar magic: Used to ensure the connection goes to the right disk
1743
  @ivar ipv6: Whether to use IPv6
1744
  @ivar connect_timeout: Number of seconds for establishing connection
1745

1746
  """
1747
  __slots__ = [
1748
    "key_name",
1749
    "ca_pem",
1750
    "compress",
1751
    "magic",
1752
    "ipv6",
1753
    "connect_timeout",
1754
    ]
1755

    
1756

    
1757
class ConfdRequest(ConfigObject):
1758
  """Object holding a confd request.
1759

1760
  @ivar protocol: confd protocol version
1761
  @ivar type: confd query type
1762
  @ivar query: query request
1763
  @ivar rsalt: requested reply salt
1764

1765
  """
1766
  __slots__ = [
1767
    "protocol",
1768
    "type",
1769
    "query",
1770
    "rsalt",
1771
    ]
1772

    
1773

    
1774
class ConfdReply(ConfigObject):
1775
  """Object holding a confd reply.
1776

1777
  @ivar protocol: confd protocol version
1778
  @ivar status: reply status code (ok, error)
1779
  @ivar answer: confd query reply
1780
  @ivar serial: configuration serial number
1781

1782
  """
1783
  __slots__ = [
1784
    "protocol",
1785
    "status",
1786
    "answer",
1787
    "serial",
1788
    ]
1789

    
1790

    
1791
class QueryFieldDefinition(ConfigObject):
1792
  """Object holding a query field definition.
1793

1794
  @ivar name: Field name
1795
  @ivar title: Human-readable title
1796
  @ivar kind: Field type
1797
  @ivar doc: Human-readable description
1798

1799
  """
1800
  __slots__ = [
1801
    "name",
1802
    "title",
1803
    "kind",
1804
    "doc",
1805
    ]
1806

    
1807

    
1808
class _QueryResponseBase(ConfigObject):
1809
  __slots__ = [
1810
    "fields",
1811
    ]
1812

    
1813
  def ToDict(self):
1814
    """Custom function for serializing.
1815

1816
    """
1817
    mydict = super(_QueryResponseBase, self).ToDict()
1818
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1819
    return mydict
1820

    
1821
  @classmethod
1822
  def FromDict(cls, val):
1823
    """Custom function for de-serializing.
1824

1825
    """
1826
    obj = super(_QueryResponseBase, cls).FromDict(val)
1827
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1828
    return obj
1829

    
1830

    
1831
class QueryRequest(ConfigObject):
1832
  """Object holding a query request.
1833

1834
  """
1835
  __slots__ = [
1836
    "what",
1837
    "fields",
1838
    "qfilter",
1839
    ]
1840

    
1841

    
1842
class QueryResponse(_QueryResponseBase):
1843
  """Object holding the response to a query.
1844

1845
  @ivar fields: List of L{QueryFieldDefinition} objects
1846
  @ivar data: Requested data
1847

1848
  """
1849
  __slots__ = [
1850
    "data",
1851
    ]
1852

    
1853

    
1854
class QueryFieldsRequest(ConfigObject):
1855
  """Object holding a request for querying available fields.
1856

1857
  """
1858
  __slots__ = [
1859
    "what",
1860
    "fields",
1861
    ]
1862

    
1863

    
1864
class QueryFieldsResponse(_QueryResponseBase):
1865
  """Object holding the response to a query for fields.
1866

1867
  @ivar fields: List of L{QueryFieldDefinition} objects
1868

1869
  """
1870
  __slots__ = [
1871
    ]
1872

    
1873

    
1874
class MigrationStatus(ConfigObject):
1875
  """Object holding the status of a migration.
1876

1877
  """
1878
  __slots__ = [
1879
    "status",
1880
    "transferred_ram",
1881
    "total_ram",
1882
    ]
1883

    
1884

    
1885
class InstanceConsole(ConfigObject):
1886
  """Object describing how to access the console of an instance.
1887

1888
  """
1889
  __slots__ = [
1890
    "instance",
1891
    "kind",
1892
    "message",
1893
    "host",
1894
    "port",
1895
    "user",
1896
    "command",
1897
    "display",
1898
    ]
1899

    
1900
  def Validate(self):
1901
    """Validates contents of this object.
1902

1903
    """
1904
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1905
    assert self.instance, "Missing instance name"
1906
    assert self.message or self.kind in [constants.CONS_SSH,
1907
                                         constants.CONS_SPICE,
1908
                                         constants.CONS_VNC]
1909
    assert self.host or self.kind == constants.CONS_MESSAGE
1910
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1911
                                      constants.CONS_SSH]
1912
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1913
                                      constants.CONS_SPICE,
1914
                                      constants.CONS_VNC]
1915
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1916
                                         constants.CONS_SPICE,
1917
                                         constants.CONS_VNC]
1918
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1919
                                         constants.CONS_SPICE,
1920
                                         constants.CONS_SSH]
1921
    return True
1922

    
1923

    
1924
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1925
  """Simple wrapper over ConfigParse that allows serialization.
1926

1927
  This class is basically ConfigParser.SafeConfigParser with two
1928
  additional methods that allow it to serialize/unserialize to/from a
1929
  buffer.
1930

1931
  """
1932
  def Dumps(self):
1933
    """Dump this instance and return the string representation."""
1934
    buf = StringIO()
1935
    self.write(buf)
1936
    return buf.getvalue()
1937

    
1938
  @classmethod
1939
  def Loads(cls, data):
1940
    """Load data from a string."""
1941
    buf = StringIO(data)
1942
    cfp = cls()
1943
    cfp.readfp(buf)
1944
    return cfp