Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 2cc673a3

History | View | Annotate | Download (54.4 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
  return ipolicy_out
228

    
229

    
230
class ConfigObject(object):
231
  """A generic config object.
232

233
  It has the following properties:
234

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

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

242
  """
243
  __slots__ = []
244

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

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

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

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

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

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

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

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

    
288
  __getstate__ = ToDict
289

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

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

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

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

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

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

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

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

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

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

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

354
    """
355
    dict_form = self.ToDict()
356
    clone_obj = self.__class__.FromDict(dict_form)
357
    return clone_obj
358

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

    
363
  def UpgradeConfig(self):
364
    """Fill defaults for missing configuration values.
365

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

369
    """
370
    pass
371

    
372

    
373
class TaggableObject(ConfigObject):
374
  """An generic class supporting tags.
375

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

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

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

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

    
398
  def GetTags(self):
399
    """Return the tags list.
400

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

    
407
  def AddTag(self, tag):
408
    """Add a new tag.
409

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

    
417
  def RemoveTag(self, tag):
418
    """Remove a tag.
419

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

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

431
    This replaces the tags set with a list.
432

433
    """
434
    bo = super(TaggableObject, self).ToDict()
435

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

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

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

    
451

    
452
class MasterNetworkParameters(ConfigObject):
453
  """Network configuration parameters for the master
454

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

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

    
470

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

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

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

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

    
494
    return mydict
495

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

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

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

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

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

    
523
  def UpgradeConfig(self):
524
    """Fill defaults for missing configuration values.
525

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

    
543

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

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

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

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

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

    
567

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

691
    This only works for VG-based disks.
692

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

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

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

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

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

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

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

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

740
    This is used only for drbd, which needs ip/port configuration.
741

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

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

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

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

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

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

784
    This replaces the children lists of objects with lists of
785
    standard python types.
786

787
    """
788
    bo = super(Disk, self).ToDict()
789

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

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

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

    
814
  def __str__(self):
815
    """Custom str() formatter for disks.
816

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

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

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

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

    
858
  def UpgradeConfig(self):
859
    """Fill defaults for missing configuration values.
860

861
    """
862
    if self.children:
863
      for child in self.children:
864
        child.UpgradeConfig()
865

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

    
873

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

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

882
    """
883
    for param in constants.ISPECS_PARAMETERS:
884
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
885
    if constants.ISPECS_DTS in ipolicy:
886
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.ISPECS_DTS])
887

    
888
  @classmethod
889
  def CheckISpecSyntax(cls, ipolicy, name):
890
    """Check the instance policy for validity on a given key.
891

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

895
    @type ipolicy: dict
896
    @param ipolicy: dictionary with min, max, std specs
897
    @type name: string
898
    @param name: what are the limits for
899
    @raise errors.ConfigureError: when specs for given name are not valid
900

901
    """
902
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
903
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
904
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
905
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
906
           (name,
907
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
908
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
909
            ipolicy[constants.ISPECS_STD].get(name, "-")))
910
    if min_v > std_v or std_v > max_v:
911
      raise errors.ConfigurationError(err)
912

    
913
  @classmethod
914
  def CheckDiskTemplates(cls, disk_templates):
915
    """Checks the disk templates for validity.
916

917
    """
918
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
919
    if wrong:
920
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
921
                                      utils.CommaJoin(wrong))
922

    
923

    
924
class Instance(TaggableObject):
925
  """Config object representing an instance."""
926
  __slots__ = [
927
    "name",
928
    "primary_node",
929
    "os",
930
    "hypervisor",
931
    "hvparams",
932
    "beparams",
933
    "osparams",
934
    "admin_state",
935
    "nics",
936
    "disks",
937
    "disk_template",
938
    "network_port",
939
    "serial_no",
940
    ] + _TIMESTAMPS + _UUID
941

    
942
  def _ComputeSecondaryNodes(self):
943
    """Compute the list of secondary nodes.
944

945
    This is a simple wrapper over _ComputeAllNodes.
946

947
    """
948
    all_nodes = set(self._ComputeAllNodes())
949
    all_nodes.discard(self.primary_node)
950
    return tuple(all_nodes)
951

    
952
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
953
                             "List of secondary nodes")
954

    
955
  def _ComputeAllNodes(self):
956
    """Compute the list of all nodes.
957

958
    Since the data is already there (in the drbd disks), keeping it as
959
    a separate normal attribute is redundant and if not properly
960
    synchronised can cause problems. Thus it's better to compute it
961
    dynamically.
962

963
    """
964
    def _Helper(nodes, device):
965
      """Recursively computes nodes given a top device."""
966
      if device.dev_type in constants.LDS_DRBD:
967
        nodea, nodeb = device.logical_id[:2]
968
        nodes.add(nodea)
969
        nodes.add(nodeb)
970
      if device.children:
971
        for child in device.children:
972
          _Helper(nodes, child)
973

    
974
    all_nodes = set()
975
    all_nodes.add(self.primary_node)
976
    for device in self.disks:
977
      _Helper(all_nodes, device)
978
    return tuple(all_nodes)
979

    
980
  all_nodes = property(_ComputeAllNodes, None, None,
981
                       "List of all nodes of the instance")
982

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

986
    This function figures out what logical volumes should belong on
987
    which nodes, recursing through a device tree.
988

989
    @param lvmap: optional dictionary to receive the
990
        'node' : ['lv', ...] data.
991

992
    @return: None if lvmap arg is given, otherwise, a dictionary of
993
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
994
        volumeN is of the form "vg_name/lv_name", compatible with
995
        GetVolumeList()
996

997
    """
998
    if node == None:
999
      node = self.primary_node
1000

    
1001
    if lvmap is None:
1002
      lvmap = {
1003
        node: [],
1004
        }
1005
      ret = lvmap
1006
    else:
1007
      if not node in lvmap:
1008
        lvmap[node] = []
1009
      ret = None
1010

    
1011
    if not devs:
1012
      devs = self.disks
1013

    
1014
    for dev in devs:
1015
      if dev.dev_type == constants.LD_LV:
1016
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1017

    
1018
      elif dev.dev_type in constants.LDS_DRBD:
1019
        if dev.children:
1020
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1021
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1022

    
1023
      elif dev.children:
1024
        self.MapLVsByNode(lvmap, dev.children, node)
1025

    
1026
    return ret
1027

    
1028
  def FindDisk(self, idx):
1029
    """Find a disk given having a specified index.
1030

1031
    This is just a wrapper that does validation of the index.
1032

1033
    @type idx: int
1034
    @param idx: the disk index
1035
    @rtype: L{Disk}
1036
    @return: the corresponding disk
1037
    @raise errors.OpPrereqError: when the given index is not valid
1038

1039
    """
1040
    try:
1041
      idx = int(idx)
1042
      return self.disks[idx]
1043
    except (TypeError, ValueError), err:
1044
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1045
                                 errors.ECODE_INVAL)
1046
    except IndexError:
1047
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1048
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1049
                                 errors.ECODE_INVAL)
1050

    
1051
  def ToDict(self):
1052
    """Instance-specific conversion to standard python types.
1053

1054
    This replaces the children lists of objects with lists of standard
1055
    python types.
1056

1057
    """
1058
    bo = super(Instance, self).ToDict()
1059

    
1060
    for attr in "nics", "disks":
1061
      alist = bo.get(attr, None)
1062
      if alist:
1063
        nlist = self._ContainerToDicts(alist)
1064
      else:
1065
        nlist = []
1066
      bo[attr] = nlist
1067
    return bo
1068

    
1069
  @classmethod
1070
  def FromDict(cls, val):
1071
    """Custom function for instances.
1072

1073
    """
1074
    if "admin_state" not in val:
1075
      if val.get("admin_up", False):
1076
        val["admin_state"] = constants.ADMINST_UP
1077
      else:
1078
        val["admin_state"] = constants.ADMINST_DOWN
1079
    if "admin_up" in val:
1080
      del val["admin_up"]
1081
    obj = super(Instance, cls).FromDict(val)
1082
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1083
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1084
    return obj
1085

    
1086
  def UpgradeConfig(self):
1087
    """Fill defaults for missing configuration values.
1088

1089
    """
1090
    for nic in self.nics:
1091
      nic.UpgradeConfig()
1092
    for disk in self.disks:
1093
      disk.UpgradeConfig()
1094
    if self.hvparams:
1095
      for key in constants.HVC_GLOBALS:
1096
        try:
1097
          del self.hvparams[key]
1098
        except KeyError:
1099
          pass
1100
    if self.osparams is None:
1101
      self.osparams = {}
1102
    UpgradeBeParams(self.beparams)
1103

    
1104

    
1105
class OS(ConfigObject):
1106
  """Config object representing an operating system.
1107

1108
  @type supported_parameters: list
1109
  @ivar supported_parameters: a list of tuples, name and description,
1110
      containing the supported parameters by this OS
1111

1112
  @type VARIANT_DELIM: string
1113
  @cvar VARIANT_DELIM: the variant delimiter
1114

1115
  """
1116
  __slots__ = [
1117
    "name",
1118
    "path",
1119
    "api_versions",
1120
    "create_script",
1121
    "export_script",
1122
    "import_script",
1123
    "rename_script",
1124
    "verify_script",
1125
    "supported_variants",
1126
    "supported_parameters",
1127
    ]
1128

    
1129
  VARIANT_DELIM = "+"
1130

    
1131
  @classmethod
1132
  def SplitNameVariant(cls, name):
1133
    """Splits the name into the proper name and variant.
1134

1135
    @param name: the OS (unprocessed) name
1136
    @rtype: list
1137
    @return: a list of two elements; if the original name didn't
1138
        contain a variant, it's returned as an empty string
1139

1140
    """
1141
    nv = name.split(cls.VARIANT_DELIM, 1)
1142
    if len(nv) == 1:
1143
      nv.append("")
1144
    return nv
1145

    
1146
  @classmethod
1147
  def GetName(cls, name):
1148
    """Returns the proper name of the os (without the variant).
1149

1150
    @param name: the OS (unprocessed) name
1151

1152
    """
1153
    return cls.SplitNameVariant(name)[0]
1154

    
1155
  @classmethod
1156
  def GetVariant(cls, name):
1157
    """Returns the variant the os (without the base name).
1158

1159
    @param name: the OS (unprocessed) name
1160

1161
    """
1162
    return cls.SplitNameVariant(name)[1]
1163

    
1164

    
1165
class NodeHvState(ConfigObject):
1166
  """Hypvervisor state on a node.
1167

1168
  @ivar mem_total: Total amount of memory
1169
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1170
    available)
1171
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1172
    rounding
1173
  @ivar mem_inst: Memory used by instances living on node
1174
  @ivar cpu_total: Total node CPU core count
1175
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1176

1177
  """
1178
  __slots__ = [
1179
    "mem_total",
1180
    "mem_node",
1181
    "mem_hv",
1182
    "mem_inst",
1183
    "cpu_total",
1184
    "cpu_node",
1185
    ] + _TIMESTAMPS
1186

    
1187

    
1188
class NodeDiskState(ConfigObject):
1189
  """Disk state on a node.
1190

1191
  """
1192
  __slots__ = [
1193
    "total",
1194
    "reserved",
1195
    "overhead",
1196
    ] + _TIMESTAMPS
1197

    
1198

    
1199
class Node(TaggableObject):
1200
  """Config object representing a node.
1201

1202
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1203
  @ivar hv_state_static: Hypervisor state overriden by user
1204
  @ivar disk_state: Disk state (e.g. free space)
1205
  @ivar disk_state_static: Disk state overriden by user
1206

1207
  """
1208
  __slots__ = [
1209
    "name",
1210
    "primary_ip",
1211
    "secondary_ip",
1212
    "serial_no",
1213
    "master_candidate",
1214
    "offline",
1215
    "drained",
1216
    "group",
1217
    "master_capable",
1218
    "vm_capable",
1219
    "ndparams",
1220
    "powered",
1221
    "hv_state",
1222
    "hv_state_static",
1223
    "disk_state",
1224
    "disk_state_static",
1225
    ] + _TIMESTAMPS + _UUID
1226

    
1227
  def UpgradeConfig(self):
1228
    """Fill defaults for missing configuration values.
1229

1230
    """
1231
    # pylint: disable=E0203
1232
    # because these are "defined" via slots, not manually
1233
    if self.master_capable is None:
1234
      self.master_capable = True
1235

    
1236
    if self.vm_capable is None:
1237
      self.vm_capable = True
1238

    
1239
    if self.ndparams is None:
1240
      self.ndparams = {}
1241

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

    
1245
  def ToDict(self):
1246
    """Custom function for serializing.
1247

1248
    """
1249
    data = super(Node, self).ToDict()
1250

    
1251
    hv_state = data.get("hv_state", None)
1252
    if hv_state is not None:
1253
      data["hv_state"] = self._ContainerToDicts(hv_state)
1254

    
1255
    disk_state = data.get("disk_state", None)
1256
    if disk_state is not None:
1257
      data["disk_state"] = \
1258
        dict((key, self._ContainerToDicts(value))
1259
             for (key, value) in disk_state.items())
1260

    
1261
    return data
1262

    
1263
  @classmethod
1264
  def FromDict(cls, val):
1265
    """Custom function for deserializing.
1266

1267
    """
1268
    obj = super(Node, cls).FromDict(val)
1269

    
1270
    if obj.hv_state is not None:
1271
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1272

    
1273
    if obj.disk_state is not None:
1274
      obj.disk_state = \
1275
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1276
             for (key, value) in obj.disk_state.items())
1277

    
1278
    return obj
1279

    
1280

    
1281
class NodeGroup(TaggableObject):
1282
  """Config object representing a node group."""
1283
  __slots__ = [
1284
    "name",
1285
    "members",
1286
    "ndparams",
1287
    "diskparams",
1288
    "ipolicy",
1289
    "serial_no",
1290
    "hv_state_static",
1291
    "disk_state_static",
1292
    "alloc_policy",
1293
    ] + _TIMESTAMPS + _UUID
1294

    
1295
  def ToDict(self):
1296
    """Custom function for nodegroup.
1297

1298
    This discards the members object, which gets recalculated and is only kept
1299
    in memory.
1300

1301
    """
1302
    mydict = super(NodeGroup, self).ToDict()
1303
    del mydict["members"]
1304
    return mydict
1305

    
1306
  @classmethod
1307
  def FromDict(cls, val):
1308
    """Custom function for nodegroup.
1309

1310
    The members slot is initialized to an empty list, upon deserialization.
1311

1312
    """
1313
    obj = super(NodeGroup, cls).FromDict(val)
1314
    obj.members = []
1315
    return obj
1316

    
1317
  def UpgradeConfig(self):
1318
    """Fill defaults for missing configuration values.
1319

1320
    """
1321
    if self.ndparams is None:
1322
      self.ndparams = {}
1323

    
1324
    if self.serial_no is None:
1325
      self.serial_no = 1
1326

    
1327
    if self.alloc_policy is None:
1328
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1329

    
1330
    # We only update mtime, and not ctime, since we would not be able
1331
    # to provide a correct value for creation time.
1332
    if self.mtime is None:
1333
      self.mtime = time.time()
1334

    
1335
    self.diskparams = UpgradeDiskParams(self.diskparams)
1336
    if self.ipolicy is None:
1337
      self.ipolicy = MakeEmptyIPolicy()
1338

    
1339
  def FillND(self, node):
1340
    """Return filled out ndparams for L{objects.Node}
1341

1342
    @type node: L{objects.Node}
1343
    @param node: A Node object to fill
1344
    @return a copy of the node's ndparams with defaults filled
1345

1346
    """
1347
    return self.SimpleFillND(node.ndparams)
1348

    
1349
  def SimpleFillND(self, ndparams):
1350
    """Fill a given ndparams dict with defaults.
1351

1352
    @type ndparams: dict
1353
    @param ndparams: the dict to fill
1354
    @rtype: dict
1355
    @return: a copy of the passed in ndparams with missing keys filled
1356
        from the node group defaults
1357

1358
    """
1359
    return FillDict(self.ndparams, ndparams)
1360

    
1361

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

    
1406
  def UpgradeConfig(self):
1407
    """Fill defaults for missing configuration values.
1408

1409
    """
1410
    # pylint: disable=E0203
1411
    # because these are "defined" via slots, not manually
1412
    if self.hvparams is None:
1413
      self.hvparams = constants.HVC_DEFAULTS
1414
    else:
1415
      for hypervisor in self.hvparams:
1416
        self.hvparams[hypervisor] = FillDict(
1417
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1418

    
1419
    if self.os_hvp is None:
1420
      self.os_hvp = {}
1421

    
1422
    # osparams added before 2.2
1423
    if self.osparams is None:
1424
      self.osparams = {}
1425

    
1426
    if self.ndparams is None:
1427
      self.ndparams = constants.NDC_DEFAULTS
1428

    
1429
    self.beparams = UpgradeGroupedParams(self.beparams,
1430
                                         constants.BEC_DEFAULTS)
1431
    for beparams_group in self.beparams:
1432
      UpgradeBeParams(self.beparams[beparams_group])
1433

    
1434
    migrate_default_bridge = not self.nicparams
1435
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1436
                                          constants.NICC_DEFAULTS)
1437
    if migrate_default_bridge:
1438
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1439
        self.default_bridge
1440

    
1441
    if self.modify_etc_hosts is None:
1442
      self.modify_etc_hosts = True
1443

    
1444
    if self.modify_ssh_setup is None:
1445
      self.modify_ssh_setup = True
1446

    
1447
    # default_bridge is no longer used in 2.1. The slot is left there to
1448
    # support auto-upgrading. It can be removed once we decide to deprecate
1449
    # upgrading straight from 2.0.
1450
    if self.default_bridge is not None:
1451
      self.default_bridge = None
1452

    
1453
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1454
    # code can be removed once upgrading straight from 2.0 is deprecated.
1455
    if self.default_hypervisor is not None:
1456
      self.enabled_hypervisors = ([self.default_hypervisor] +
1457
        [hvname for hvname in self.enabled_hypervisors
1458
         if hvname != self.default_hypervisor])
1459
      self.default_hypervisor = None
1460

    
1461
    # maintain_node_health added after 2.1.1
1462
    if self.maintain_node_health is None:
1463
      self.maintain_node_health = False
1464

    
1465
    if self.uid_pool is None:
1466
      self.uid_pool = []
1467

    
1468
    if self.default_iallocator is None:
1469
      self.default_iallocator = ""
1470

    
1471
    # reserved_lvs added before 2.2
1472
    if self.reserved_lvs is None:
1473
      self.reserved_lvs = []
1474

    
1475
    # hidden and blacklisted operating systems added before 2.2.1
1476
    if self.hidden_os is None:
1477
      self.hidden_os = []
1478

    
1479
    if self.blacklisted_os is None:
1480
      self.blacklisted_os = []
1481

    
1482
    # primary_ip_family added before 2.3
1483
    if self.primary_ip_family is None:
1484
      self.primary_ip_family = AF_INET
1485

    
1486
    if self.master_netmask is None:
1487
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1488
      self.master_netmask = ipcls.iplen
1489

    
1490
    if self.prealloc_wipe_disks is None:
1491
      self.prealloc_wipe_disks = False
1492

    
1493
    # shared_file_storage_dir added before 2.5
1494
    if self.shared_file_storage_dir is None:
1495
      self.shared_file_storage_dir = ""
1496

    
1497
    if self.use_external_mip_script is None:
1498
      self.use_external_mip_script = False
1499

    
1500
    self.diskparams = UpgradeDiskParams(self.diskparams)
1501

    
1502
    # instance policy added before 2.6
1503
    if self.ipolicy is None:
1504
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1505

    
1506
  @property
1507
  def primary_hypervisor(self):
1508
    """The first hypervisor is the primary.
1509

1510
    Useful, for example, for L{Node}'s hv/disk state.
1511

1512
    """
1513
    return self.enabled_hypervisors[0]
1514

    
1515
  def ToDict(self):
1516
    """Custom function for cluster.
1517

1518
    """
1519
    mydict = super(Cluster, self).ToDict()
1520
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1521
    return mydict
1522

    
1523
  @classmethod
1524
  def FromDict(cls, val):
1525
    """Custom function for cluster.
1526

1527
    """
1528
    obj = super(Cluster, cls).FromDict(val)
1529
    if not isinstance(obj.tcpudp_port_pool, set):
1530
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1531
    return obj
1532

    
1533
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1534
    """Get the default hypervisor parameters for the cluster.
1535

1536
    @param hypervisor: the hypervisor name
1537
    @param os_name: if specified, we'll also update the defaults for this OS
1538
    @param skip_keys: if passed, list of keys not to use
1539
    @return: the defaults dict
1540

1541
    """
1542
    if skip_keys is None:
1543
      skip_keys = []
1544

    
1545
    fill_stack = [self.hvparams.get(hypervisor, {})]
1546
    if os_name is not None:
1547
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1548
      fill_stack.append(os_hvp)
1549

    
1550
    ret_dict = {}
1551
    for o_dict in fill_stack:
1552
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1553

    
1554
    return ret_dict
1555

    
1556
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1557
    """Fill a given hvparams dict with cluster defaults.
1558

1559
    @type hv_name: string
1560
    @param hv_name: the hypervisor to use
1561
    @type os_name: string
1562
    @param os_name: the OS to use for overriding the hypervisor defaults
1563
    @type skip_globals: boolean
1564
    @param skip_globals: if True, the global hypervisor parameters will
1565
        not be filled
1566
    @rtype: dict
1567
    @return: a copy of the given hvparams with missing keys filled from
1568
        the cluster defaults
1569

1570
    """
1571
    if skip_globals:
1572
      skip_keys = constants.HVC_GLOBALS
1573
    else:
1574
      skip_keys = []
1575

    
1576
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1577
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1578

    
1579
  def FillHV(self, instance, skip_globals=False):
1580
    """Fill an instance's hvparams dict with cluster defaults.
1581

1582
    @type instance: L{objects.Instance}
1583
    @param instance: the instance parameter to fill
1584
    @type skip_globals: boolean
1585
    @param skip_globals: if True, the global hypervisor parameters will
1586
        not be filled
1587
    @rtype: dict
1588
    @return: a copy of the instance's hvparams with missing keys filled from
1589
        the cluster defaults
1590

1591
    """
1592
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1593
                             instance.hvparams, skip_globals)
1594

    
1595
  def SimpleFillBE(self, beparams):
1596
    """Fill a given beparams dict with cluster defaults.
1597

1598
    @type beparams: dict
1599
    @param beparams: the dict to fill
1600
    @rtype: dict
1601
    @return: a copy of the passed in beparams with missing keys filled
1602
        from the cluster defaults
1603

1604
    """
1605
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1606

    
1607
  def FillBE(self, instance):
1608
    """Fill an instance's beparams dict with cluster defaults.
1609

1610
    @type instance: L{objects.Instance}
1611
    @param instance: the instance parameter to fill
1612
    @rtype: dict
1613
    @return: a copy of the instance's beparams with missing keys filled from
1614
        the cluster defaults
1615

1616
    """
1617
    return self.SimpleFillBE(instance.beparams)
1618

    
1619
  def SimpleFillNIC(self, nicparams):
1620
    """Fill a given nicparams dict with cluster defaults.
1621

1622
    @type nicparams: dict
1623
    @param nicparams: the dict to fill
1624
    @rtype: dict
1625
    @return: a copy of the passed in nicparams with missing keys filled
1626
        from the cluster defaults
1627

1628
    """
1629
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1630

    
1631
  def SimpleFillOS(self, os_name, os_params):
1632
    """Fill an instance's osparams dict with cluster defaults.
1633

1634
    @type os_name: string
1635
    @param os_name: the OS name to use
1636
    @type os_params: dict
1637
    @param os_params: the dict to fill with default values
1638
    @rtype: dict
1639
    @return: a copy of the instance's osparams with missing keys filled from
1640
        the cluster defaults
1641

1642
    """
1643
    name_only = os_name.split("+", 1)[0]
1644
    # base OS
1645
    result = self.osparams.get(name_only, {})
1646
    # OS with variant
1647
    result = FillDict(result, self.osparams.get(os_name, {}))
1648
    # specified params
1649
    return FillDict(result, os_params)
1650

    
1651
  @staticmethod
1652
  def SimpleFillHvState(hv_state):
1653
    """Fill an hv_state sub dict with cluster defaults.
1654

1655
    """
1656
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1657

    
1658
  @staticmethod
1659
  def SimpleFillDiskState(disk_state):
1660
    """Fill an disk_state sub dict with cluster defaults.
1661

1662
    """
1663
    return FillDict(constants.DS_DEFAULTS, disk_state)
1664

    
1665
  def FillND(self, node, nodegroup):
1666
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1667

1668
    @type node: L{objects.Node}
1669
    @param node: A Node object to fill
1670
    @type nodegroup: L{objects.NodeGroup}
1671
    @param nodegroup: A Node object to fill
1672
    @return a copy of the node's ndparams with defaults filled
1673

1674
    """
1675
    return self.SimpleFillND(nodegroup.FillND(node))
1676

    
1677
  def SimpleFillND(self, ndparams):
1678
    """Fill a given ndparams dict with defaults.
1679

1680
    @type ndparams: dict
1681
    @param ndparams: the dict to fill
1682
    @rtype: dict
1683
    @return: a copy of the passed in ndparams with missing keys filled
1684
        from the cluster defaults
1685

1686
    """
1687
    return FillDict(self.ndparams, ndparams)
1688

    
1689
  def SimpleFillIPolicy(self, ipolicy):
1690
    """ Fill instance policy dict with defaults.
1691

1692
    @type ipolicy: dict
1693
    @param ipolicy: the dict to fill
1694
    @rtype: dict
1695
    @return: a copy of passed ipolicy with missing keys filled from
1696
      the cluster defaults
1697

1698
    """
1699
    return FillIPolicy(self.ipolicy, ipolicy)
1700

    
1701

    
1702
class BlockDevStatus(ConfigObject):
1703
  """Config object representing the status of a block device."""
1704
  __slots__ = [
1705
    "dev_path",
1706
    "major",
1707
    "minor",
1708
    "sync_percent",
1709
    "estimated_time",
1710
    "is_degraded",
1711
    "ldisk_status",
1712
    ]
1713

    
1714

    
1715
class ImportExportStatus(ConfigObject):
1716
  """Config object representing the status of an import or export."""
1717
  __slots__ = [
1718
    "recent_output",
1719
    "listen_port",
1720
    "connected",
1721
    "progress_mbytes",
1722
    "progress_throughput",
1723
    "progress_eta",
1724
    "progress_percent",
1725
    "exit_status",
1726
    "error_message",
1727
    ] + _TIMESTAMPS
1728

    
1729

    
1730
class ImportExportOptions(ConfigObject):
1731
  """Options for import/export daemon
1732

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

1740
  """
1741
  __slots__ = [
1742
    "key_name",
1743
    "ca_pem",
1744
    "compress",
1745
    "magic",
1746
    "ipv6",
1747
    "connect_timeout",
1748
    ]
1749

    
1750

    
1751
class ConfdRequest(ConfigObject):
1752
  """Object holding a confd request.
1753

1754
  @ivar protocol: confd protocol version
1755
  @ivar type: confd query type
1756
  @ivar query: query request
1757
  @ivar rsalt: requested reply salt
1758

1759
  """
1760
  __slots__ = [
1761
    "protocol",
1762
    "type",
1763
    "query",
1764
    "rsalt",
1765
    ]
1766

    
1767

    
1768
class ConfdReply(ConfigObject):
1769
  """Object holding a confd reply.
1770

1771
  @ivar protocol: confd protocol version
1772
  @ivar status: reply status code (ok, error)
1773
  @ivar answer: confd query reply
1774
  @ivar serial: configuration serial number
1775

1776
  """
1777
  __slots__ = [
1778
    "protocol",
1779
    "status",
1780
    "answer",
1781
    "serial",
1782
    ]
1783

    
1784

    
1785
class QueryFieldDefinition(ConfigObject):
1786
  """Object holding a query field definition.
1787

1788
  @ivar name: Field name
1789
  @ivar title: Human-readable title
1790
  @ivar kind: Field type
1791
  @ivar doc: Human-readable description
1792

1793
  """
1794
  __slots__ = [
1795
    "name",
1796
    "title",
1797
    "kind",
1798
    "doc",
1799
    ]
1800

    
1801

    
1802
class _QueryResponseBase(ConfigObject):
1803
  __slots__ = [
1804
    "fields",
1805
    ]
1806

    
1807
  def ToDict(self):
1808
    """Custom function for serializing.
1809

1810
    """
1811
    mydict = super(_QueryResponseBase, self).ToDict()
1812
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1813
    return mydict
1814

    
1815
  @classmethod
1816
  def FromDict(cls, val):
1817
    """Custom function for de-serializing.
1818

1819
    """
1820
    obj = super(_QueryResponseBase, cls).FromDict(val)
1821
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1822
    return obj
1823

    
1824

    
1825
class QueryRequest(ConfigObject):
1826
  """Object holding a query request.
1827

1828
  """
1829
  __slots__ = [
1830
    "what",
1831
    "fields",
1832
    "qfilter",
1833
    ]
1834

    
1835

    
1836
class QueryResponse(_QueryResponseBase):
1837
  """Object holding the response to a query.
1838

1839
  @ivar fields: List of L{QueryFieldDefinition} objects
1840
  @ivar data: Requested data
1841

1842
  """
1843
  __slots__ = [
1844
    "data",
1845
    ]
1846

    
1847

    
1848
class QueryFieldsRequest(ConfigObject):
1849
  """Object holding a request for querying available fields.
1850

1851
  """
1852
  __slots__ = [
1853
    "what",
1854
    "fields",
1855
    ]
1856

    
1857

    
1858
class QueryFieldsResponse(_QueryResponseBase):
1859
  """Object holding the response to a query for fields.
1860

1861
  @ivar fields: List of L{QueryFieldDefinition} objects
1862

1863
  """
1864
  __slots__ = [
1865
    ]
1866

    
1867

    
1868
class MigrationStatus(ConfigObject):
1869
  """Object holding the status of a migration.
1870

1871
  """
1872
  __slots__ = [
1873
    "status",
1874
    "transferred_ram",
1875
    "total_ram",
1876
    ]
1877

    
1878

    
1879
class InstanceConsole(ConfigObject):
1880
  """Object describing how to access the console of an instance.
1881

1882
  """
1883
  __slots__ = [
1884
    "instance",
1885
    "kind",
1886
    "message",
1887
    "host",
1888
    "port",
1889
    "user",
1890
    "command",
1891
    "display",
1892
    ]
1893

    
1894
  def Validate(self):
1895
    """Validates contents of this object.
1896

1897
    """
1898
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1899
    assert self.instance, "Missing instance name"
1900
    assert self.message or self.kind in [constants.CONS_SSH,
1901
                                         constants.CONS_SPICE,
1902
                                         constants.CONS_VNC]
1903
    assert self.host or self.kind == constants.CONS_MESSAGE
1904
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1905
                                      constants.CONS_SSH]
1906
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1907
                                      constants.CONS_SPICE,
1908
                                      constants.CONS_VNC]
1909
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1910
                                         constants.CONS_SPICE,
1911
                                         constants.CONS_VNC]
1912
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1913
                                         constants.CONS_SPICE,
1914
                                         constants.CONS_SSH]
1915
    return True
1916

    
1917

    
1918
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1919
  """Simple wrapper over ConfigParse that allows serialization.
1920

1921
  This class is basically ConfigParser.SafeConfigParser with two
1922
  additional methods that allow it to serialize/unserialize to/from a
1923
  buffer.
1924

1925
  """
1926
  def Dumps(self):
1927
    """Dump this instance and return the string representation."""
1928
    buf = StringIO()
1929
    self.write(buf)
1930
    return buf.getvalue()
1931

    
1932
  @classmethod
1933
  def Loads(cls, data):
1934
    """Load data from a string."""
1935
    buf = StringIO(data)
1936
    cfp = cls()
1937
    cfp.readfp(buf)
1938
    return cfp