Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 735e1318

History | View | Annotate | Download (55.5 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.IPOLICY_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
                          ipolicy_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 ipolicy_disk_templates is None:
223
    ipolicy_disk_templates = constants.DISK_TEMPLATES
224
  if ipolicy_disk_templates is not None:
225
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_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
    elif self.dev_type == constants.LD_RBD:
603
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
604
    return None
605

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

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

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

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

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

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

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

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

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

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

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

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

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

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

695
    This only works for VG-based disks.
696

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

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

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

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

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

    
733
  def Update(self, size=None, mode=None):
734
    """Apply changes to size and mode.
735

736
    """
737
    if self.dev_type == constants.LD_DRBD8:
738
      if self.children:
739
        self.children[0].Update(size=size, mode=mode)
740
    else:
741
      assert not self.children
742

    
743
    if size is not None:
744
      self.size = size
745
    if mode is not None:
746
      self.mode = mode
747

    
748
  def UnsetSize(self):
749
    """Sets recursively the size to zero for the disk and its children.
750

751
    """
752
    if self.children:
753
      for child in self.children:
754
        child.UnsetSize()
755
    self.size = 0
756

    
757
  def SetPhysicalID(self, target_node, nodes_ip):
758
    """Convert the logical ID to the physical ID.
759

760
    This is used only for drbd, which needs ip/port configuration.
761

762
    The routine descends down and updates its children also, because
763
    this helps when the only the top device is passed to the remote
764
    node.
765

766
    Arguments:
767
      - target_node: the node we wish to configure for
768
      - nodes_ip: a mapping of node name to ip
769

770
    The target_node must exist in in nodes_ip, and must be one of the
771
    nodes in the logical ID for each of the DRBD devices encountered
772
    in the disk tree.
773

774
    """
775
    if self.children:
776
      for child in self.children:
777
        child.SetPhysicalID(target_node, nodes_ip)
778

    
779
    if self.logical_id is None and self.physical_id is not None:
780
      return
781
    if self.dev_type in constants.LDS_DRBD:
782
      pnode, snode, port, pminor, sminor, secret = self.logical_id
783
      if target_node not in (pnode, snode):
784
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
785
                                        target_node)
786
      pnode_ip = nodes_ip.get(pnode, None)
787
      snode_ip = nodes_ip.get(snode, None)
788
      if pnode_ip is None or snode_ip is None:
789
        raise errors.ConfigurationError("Can't find primary or secondary node"
790
                                        " for %s" % str(self))
791
      p_data = (pnode_ip, port)
792
      s_data = (snode_ip, port)
793
      if pnode == target_node:
794
        self.physical_id = p_data + s_data + (pminor, secret)
795
      else: # it must be secondary, we tested above
796
        self.physical_id = s_data + p_data + (sminor, secret)
797
    else:
798
      self.physical_id = self.logical_id
799
    return
800

    
801
  def ToDict(self):
802
    """Disk-specific conversion to standard python types.
803

804
    This replaces the children lists of objects with lists of
805
    standard python types.
806

807
    """
808
    bo = super(Disk, self).ToDict()
809

    
810
    for attr in ("children",):
811
      alist = bo.get(attr, None)
812
      if alist:
813
        bo[attr] = self._ContainerToDicts(alist)
814
    return bo
815

    
816
  @classmethod
817
  def FromDict(cls, val):
818
    """Custom function for Disks
819

820
    """
821
    obj = super(Disk, cls).FromDict(val)
822
    if obj.children:
823
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
824
    if obj.logical_id and isinstance(obj.logical_id, list):
825
      obj.logical_id = tuple(obj.logical_id)
826
    if obj.physical_id and isinstance(obj.physical_id, list):
827
      obj.physical_id = tuple(obj.physical_id)
828
    if obj.dev_type in constants.LDS_DRBD:
829
      # we need a tuple of length six here
830
      if len(obj.logical_id) < 6:
831
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
832
    return obj
833

    
834
  def __str__(self):
835
    """Custom str() formatter for disks.
836

837
    """
838
    if self.dev_type == constants.LD_LV:
839
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
840
    elif self.dev_type in constants.LDS_DRBD:
841
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
842
      val = "<DRBD8("
843
      if self.physical_id is None:
844
        phy = "unconfigured"
845
      else:
846
        phy = ("configured as %s:%s %s:%s" %
847
               (self.physical_id[0], self.physical_id[1],
848
                self.physical_id[2], self.physical_id[3]))
849

    
850
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
851
              (node_a, minor_a, node_b, minor_b, port, phy))
852
      if self.children and self.children.count(None) == 0:
853
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
854
      else:
855
        val += "no local storage"
856
    else:
857
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
858
             (self.dev_type, self.logical_id, self.physical_id, self.children))
859
    if self.iv_name is None:
860
      val += ", not visible"
861
    else:
862
      val += ", visible as /dev/%s" % self.iv_name
863
    if isinstance(self.size, int):
864
      val += ", size=%dm)>" % self.size
865
    else:
866
      val += ", size='%s')>" % (self.size,)
867
    return val
868

    
869
  def Verify(self):
870
    """Checks that this disk is correctly configured.
871

872
    """
873
    all_errors = []
874
    if self.mode not in constants.DISK_ACCESS_SET:
875
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
876
    return all_errors
877

    
878
  def UpgradeConfig(self):
879
    """Fill defaults for missing configuration values.
880

881
    """
882
    if self.children:
883
      for child in self.children:
884
        child.UpgradeConfig()
885

    
886
    if not self.params:
887
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
888
    else:
889
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
890
                             self.params)
891
    # add here config upgrade for this disk
892

    
893

    
894
class InstancePolicy(ConfigObject):
895
  """Config object representing instance policy limits dictionary."""
896
  __slots__ = ["min", "max", "std", "disk_templates"]
897

    
898
  @classmethod
899
  def CheckParameterSyntax(cls, ipolicy):
900
    """ Check the instance policy for validity.
901

902
    """
903
    for param in constants.ISPECS_PARAMETERS:
904
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
905
    if constants.IPOLICY_DTS in ipolicy:
906
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
907
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
908
    if wrong_keys:
909
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
910
                                      utils.CommaJoin(wrong_keys))
911

    
912
  @classmethod
913
  def CheckISpecSyntax(cls, ipolicy, name):
914
    """Check the instance policy for validity on a given key.
915

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

919
    @type ipolicy: dict
920
    @param ipolicy: dictionary with min, max, std specs
921
    @type name: string
922
    @param name: what are the limits for
923
    @raise errors.ConfigureError: when specs for given name are not valid
924

925
    """
926
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
927
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
928
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
929
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
930
           (name,
931
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
932
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
933
            ipolicy[constants.ISPECS_STD].get(name, "-")))
934
    if min_v > std_v or std_v > max_v:
935
      raise errors.ConfigurationError(err)
936

    
937
  @classmethod
938
  def CheckDiskTemplates(cls, disk_templates):
939
    """Checks the disk templates for validity.
940

941
    """
942
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
943
    if wrong:
944
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
945
                                      utils.CommaJoin(wrong))
946

    
947

    
948
class Instance(TaggableObject):
949
  """Config object representing an instance."""
950
  __slots__ = [
951
    "name",
952
    "primary_node",
953
    "os",
954
    "hypervisor",
955
    "hvparams",
956
    "beparams",
957
    "osparams",
958
    "admin_state",
959
    "nics",
960
    "disks",
961
    "disk_template",
962
    "network_port",
963
    "serial_no",
964
    ] + _TIMESTAMPS + _UUID
965

    
966
  def _ComputeSecondaryNodes(self):
967
    """Compute the list of secondary nodes.
968

969
    This is a simple wrapper over _ComputeAllNodes.
970

971
    """
972
    all_nodes = set(self._ComputeAllNodes())
973
    all_nodes.discard(self.primary_node)
974
    return tuple(all_nodes)
975

    
976
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
977
                             "List of secondary nodes")
978

    
979
  def _ComputeAllNodes(self):
980
    """Compute the list of all nodes.
981

982
    Since the data is already there (in the drbd disks), keeping it as
983
    a separate normal attribute is redundant and if not properly
984
    synchronised can cause problems. Thus it's better to compute it
985
    dynamically.
986

987
    """
988
    def _Helper(nodes, device):
989
      """Recursively computes nodes given a top device."""
990
      if device.dev_type in constants.LDS_DRBD:
991
        nodea, nodeb = device.logical_id[:2]
992
        nodes.add(nodea)
993
        nodes.add(nodeb)
994
      if device.children:
995
        for child in device.children:
996
          _Helper(nodes, child)
997

    
998
    all_nodes = set()
999
    all_nodes.add(self.primary_node)
1000
    for device in self.disks:
1001
      _Helper(all_nodes, device)
1002
    return tuple(all_nodes)
1003

    
1004
  all_nodes = property(_ComputeAllNodes, None, None,
1005
                       "List of all nodes of the instance")
1006

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

1010
    This function figures out what logical volumes should belong on
1011
    which nodes, recursing through a device tree.
1012

1013
    @param lvmap: optional dictionary to receive the
1014
        'node' : ['lv', ...] data.
1015

1016
    @return: None if lvmap arg is given, otherwise, a dictionary of
1017
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1018
        volumeN is of the form "vg_name/lv_name", compatible with
1019
        GetVolumeList()
1020

1021
    """
1022
    if node == None:
1023
      node = self.primary_node
1024

    
1025
    if lvmap is None:
1026
      lvmap = {
1027
        node: [],
1028
        }
1029
      ret = lvmap
1030
    else:
1031
      if not node in lvmap:
1032
        lvmap[node] = []
1033
      ret = None
1034

    
1035
    if not devs:
1036
      devs = self.disks
1037

    
1038
    for dev in devs:
1039
      if dev.dev_type == constants.LD_LV:
1040
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1041

    
1042
      elif dev.dev_type in constants.LDS_DRBD:
1043
        if dev.children:
1044
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1045
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1046

    
1047
      elif dev.children:
1048
        self.MapLVsByNode(lvmap, dev.children, node)
1049

    
1050
    return ret
1051

    
1052
  def FindDisk(self, idx):
1053
    """Find a disk given having a specified index.
1054

1055
    This is just a wrapper that does validation of the index.
1056

1057
    @type idx: int
1058
    @param idx: the disk index
1059
    @rtype: L{Disk}
1060
    @return: the corresponding disk
1061
    @raise errors.OpPrereqError: when the given index is not valid
1062

1063
    """
1064
    try:
1065
      idx = int(idx)
1066
      return self.disks[idx]
1067
    except (TypeError, ValueError), err:
1068
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1069
                                 errors.ECODE_INVAL)
1070
    except IndexError:
1071
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1072
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1073
                                 errors.ECODE_INVAL)
1074

    
1075
  def ToDict(self):
1076
    """Instance-specific conversion to standard python types.
1077

1078
    This replaces the children lists of objects with lists of standard
1079
    python types.
1080

1081
    """
1082
    bo = super(Instance, self).ToDict()
1083

    
1084
    for attr in "nics", "disks":
1085
      alist = bo.get(attr, None)
1086
      if alist:
1087
        nlist = self._ContainerToDicts(alist)
1088
      else:
1089
        nlist = []
1090
      bo[attr] = nlist
1091
    return bo
1092

    
1093
  @classmethod
1094
  def FromDict(cls, val):
1095
    """Custom function for instances.
1096

1097
    """
1098
    if "admin_state" not in val:
1099
      if val.get("admin_up", False):
1100
        val["admin_state"] = constants.ADMINST_UP
1101
      else:
1102
        val["admin_state"] = constants.ADMINST_DOWN
1103
    if "admin_up" in val:
1104
      del val["admin_up"]
1105
    obj = super(Instance, cls).FromDict(val)
1106
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1107
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1108
    return obj
1109

    
1110
  def UpgradeConfig(self):
1111
    """Fill defaults for missing configuration values.
1112

1113
    """
1114
    for nic in self.nics:
1115
      nic.UpgradeConfig()
1116
    for disk in self.disks:
1117
      disk.UpgradeConfig()
1118
    if self.hvparams:
1119
      for key in constants.HVC_GLOBALS:
1120
        try:
1121
          del self.hvparams[key]
1122
        except KeyError:
1123
          pass
1124
    if self.osparams is None:
1125
      self.osparams = {}
1126
    UpgradeBeParams(self.beparams)
1127

    
1128

    
1129
class OS(ConfigObject):
1130
  """Config object representing an operating system.
1131

1132
  @type supported_parameters: list
1133
  @ivar supported_parameters: a list of tuples, name and description,
1134
      containing the supported parameters by this OS
1135

1136
  @type VARIANT_DELIM: string
1137
  @cvar VARIANT_DELIM: the variant delimiter
1138

1139
  """
1140
  __slots__ = [
1141
    "name",
1142
    "path",
1143
    "api_versions",
1144
    "create_script",
1145
    "export_script",
1146
    "import_script",
1147
    "rename_script",
1148
    "verify_script",
1149
    "supported_variants",
1150
    "supported_parameters",
1151
    ]
1152

    
1153
  VARIANT_DELIM = "+"
1154

    
1155
  @classmethod
1156
  def SplitNameVariant(cls, name):
1157
    """Splits the name into the proper name and variant.
1158

1159
    @param name: the OS (unprocessed) name
1160
    @rtype: list
1161
    @return: a list of two elements; if the original name didn't
1162
        contain a variant, it's returned as an empty string
1163

1164
    """
1165
    nv = name.split(cls.VARIANT_DELIM, 1)
1166
    if len(nv) == 1:
1167
      nv.append("")
1168
    return nv
1169

    
1170
  @classmethod
1171
  def GetName(cls, name):
1172
    """Returns the proper name of the os (without the variant).
1173

1174
    @param name: the OS (unprocessed) name
1175

1176
    """
1177
    return cls.SplitNameVariant(name)[0]
1178

    
1179
  @classmethod
1180
  def GetVariant(cls, name):
1181
    """Returns the variant the os (without the base name).
1182

1183
    @param name: the OS (unprocessed) name
1184

1185
    """
1186
    return cls.SplitNameVariant(name)[1]
1187

    
1188

    
1189
class NodeHvState(ConfigObject):
1190
  """Hypvervisor state on a node.
1191

1192
  @ivar mem_total: Total amount of memory
1193
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1194
    available)
1195
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1196
    rounding
1197
  @ivar mem_inst: Memory used by instances living on node
1198
  @ivar cpu_total: Total node CPU core count
1199
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1200

1201
  """
1202
  __slots__ = [
1203
    "mem_total",
1204
    "mem_node",
1205
    "mem_hv",
1206
    "mem_inst",
1207
    "cpu_total",
1208
    "cpu_node",
1209
    ] + _TIMESTAMPS
1210

    
1211

    
1212
class NodeDiskState(ConfigObject):
1213
  """Disk state on a node.
1214

1215
  """
1216
  __slots__ = [
1217
    "total",
1218
    "reserved",
1219
    "overhead",
1220
    ] + _TIMESTAMPS
1221

    
1222

    
1223
class Node(TaggableObject):
1224
  """Config object representing a node.
1225

1226
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1227
  @ivar hv_state_static: Hypervisor state overriden by user
1228
  @ivar disk_state: Disk state (e.g. free space)
1229
  @ivar disk_state_static: Disk state overriden by user
1230

1231
  """
1232
  __slots__ = [
1233
    "name",
1234
    "primary_ip",
1235
    "secondary_ip",
1236
    "serial_no",
1237
    "master_candidate",
1238
    "offline",
1239
    "drained",
1240
    "group",
1241
    "master_capable",
1242
    "vm_capable",
1243
    "ndparams",
1244
    "powered",
1245
    "hv_state",
1246
    "hv_state_static",
1247
    "disk_state",
1248
    "disk_state_static",
1249
    ] + _TIMESTAMPS + _UUID
1250

    
1251
  def UpgradeConfig(self):
1252
    """Fill defaults for missing configuration values.
1253

1254
    """
1255
    # pylint: disable=E0203
1256
    # because these are "defined" via slots, not manually
1257
    if self.master_capable is None:
1258
      self.master_capable = True
1259

    
1260
    if self.vm_capable is None:
1261
      self.vm_capable = True
1262

    
1263
    if self.ndparams is None:
1264
      self.ndparams = {}
1265

    
1266
    if self.powered is None:
1267
      self.powered = True
1268

    
1269
  def ToDict(self):
1270
    """Custom function for serializing.
1271

1272
    """
1273
    data = super(Node, self).ToDict()
1274

    
1275
    hv_state = data.get("hv_state", None)
1276
    if hv_state is not None:
1277
      data["hv_state"] = self._ContainerToDicts(hv_state)
1278

    
1279
    disk_state = data.get("disk_state", None)
1280
    if disk_state is not None:
1281
      data["disk_state"] = \
1282
        dict((key, self._ContainerToDicts(value))
1283
             for (key, value) in disk_state.items())
1284

    
1285
    return data
1286

    
1287
  @classmethod
1288
  def FromDict(cls, val):
1289
    """Custom function for deserializing.
1290

1291
    """
1292
    obj = super(Node, cls).FromDict(val)
1293

    
1294
    if obj.hv_state is not None:
1295
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1296

    
1297
    if obj.disk_state is not None:
1298
      obj.disk_state = \
1299
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1300
             for (key, value) in obj.disk_state.items())
1301

    
1302
    return obj
1303

    
1304

    
1305
class NodeGroup(TaggableObject):
1306
  """Config object representing a node group."""
1307
  __slots__ = [
1308
    "name",
1309
    "members",
1310
    "ndparams",
1311
    "diskparams",
1312
    "ipolicy",
1313
    "serial_no",
1314
    "hv_state_static",
1315
    "disk_state_static",
1316
    "alloc_policy",
1317
    ] + _TIMESTAMPS + _UUID
1318

    
1319
  def ToDict(self):
1320
    """Custom function for nodegroup.
1321

1322
    This discards the members object, which gets recalculated and is only kept
1323
    in memory.
1324

1325
    """
1326
    mydict = super(NodeGroup, self).ToDict()
1327
    del mydict["members"]
1328
    return mydict
1329

    
1330
  @classmethod
1331
  def FromDict(cls, val):
1332
    """Custom function for nodegroup.
1333

1334
    The members slot is initialized to an empty list, upon deserialization.
1335

1336
    """
1337
    obj = super(NodeGroup, cls).FromDict(val)
1338
    obj.members = []
1339
    return obj
1340

    
1341
  def UpgradeConfig(self):
1342
    """Fill defaults for missing configuration values.
1343

1344
    """
1345
    if self.ndparams is None:
1346
      self.ndparams = {}
1347

    
1348
    if self.serial_no is None:
1349
      self.serial_no = 1
1350

    
1351
    if self.alloc_policy is None:
1352
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1353

    
1354
    # We only update mtime, and not ctime, since we would not be able
1355
    # to provide a correct value for creation time.
1356
    if self.mtime is None:
1357
      self.mtime = time.time()
1358

    
1359
    self.diskparams = UpgradeDiskParams(self.diskparams)
1360
    if self.ipolicy is None:
1361
      self.ipolicy = MakeEmptyIPolicy()
1362

    
1363
  def FillND(self, node):
1364
    """Return filled out ndparams for L{objects.Node}
1365

1366
    @type node: L{objects.Node}
1367
    @param node: A Node object to fill
1368
    @return a copy of the node's ndparams with defaults filled
1369

1370
    """
1371
    return self.SimpleFillND(node.ndparams)
1372

    
1373
  def SimpleFillND(self, ndparams):
1374
    """Fill a given ndparams dict with defaults.
1375

1376
    @type ndparams: dict
1377
    @param ndparams: the dict to fill
1378
    @rtype: dict
1379
    @return: a copy of the passed in ndparams with missing keys filled
1380
        from the node group defaults
1381

1382
    """
1383
    return FillDict(self.ndparams, ndparams)
1384

    
1385

    
1386
class Cluster(TaggableObject):
1387
  """Config object representing the cluster."""
1388
  __slots__ = [
1389
    "serial_no",
1390
    "rsahostkeypub",
1391
    "highest_used_port",
1392
    "tcpudp_port_pool",
1393
    "mac_prefix",
1394
    "volume_group_name",
1395
    "reserved_lvs",
1396
    "drbd_usermode_helper",
1397
    "default_bridge",
1398
    "default_hypervisor",
1399
    "master_node",
1400
    "master_ip",
1401
    "master_netdev",
1402
    "master_netmask",
1403
    "use_external_mip_script",
1404
    "cluster_name",
1405
    "file_storage_dir",
1406
    "shared_file_storage_dir",
1407
    "enabled_hypervisors",
1408
    "hvparams",
1409
    "ipolicy",
1410
    "os_hvp",
1411
    "beparams",
1412
    "osparams",
1413
    "nicparams",
1414
    "ndparams",
1415
    "diskparams",
1416
    "candidate_pool_size",
1417
    "modify_etc_hosts",
1418
    "modify_ssh_setup",
1419
    "maintain_node_health",
1420
    "uid_pool",
1421
    "default_iallocator",
1422
    "hidden_os",
1423
    "blacklisted_os",
1424
    "primary_ip_family",
1425
    "prealloc_wipe_disks",
1426
    "hv_state_static",
1427
    "disk_state_static",
1428
    ] + _TIMESTAMPS + _UUID
1429

    
1430
  def UpgradeConfig(self):
1431
    """Fill defaults for missing configuration values.
1432

1433
    """
1434
    # pylint: disable=E0203
1435
    # because these are "defined" via slots, not manually
1436
    if self.hvparams is None:
1437
      self.hvparams = constants.HVC_DEFAULTS
1438
    else:
1439
      for hypervisor in self.hvparams:
1440
        self.hvparams[hypervisor] = FillDict(
1441
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1442

    
1443
    if self.os_hvp is None:
1444
      self.os_hvp = {}
1445

    
1446
    # osparams added before 2.2
1447
    if self.osparams is None:
1448
      self.osparams = {}
1449

    
1450
    if self.ndparams is None:
1451
      self.ndparams = constants.NDC_DEFAULTS
1452

    
1453
    self.beparams = UpgradeGroupedParams(self.beparams,
1454
                                         constants.BEC_DEFAULTS)
1455
    for beparams_group in self.beparams:
1456
      UpgradeBeParams(self.beparams[beparams_group])
1457

    
1458
    migrate_default_bridge = not self.nicparams
1459
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1460
                                          constants.NICC_DEFAULTS)
1461
    if migrate_default_bridge:
1462
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1463
        self.default_bridge
1464

    
1465
    if self.modify_etc_hosts is None:
1466
      self.modify_etc_hosts = True
1467

    
1468
    if self.modify_ssh_setup is None:
1469
      self.modify_ssh_setup = True
1470

    
1471
    # default_bridge is no longer used in 2.1. The slot is left there to
1472
    # support auto-upgrading. It can be removed once we decide to deprecate
1473
    # upgrading straight from 2.0.
1474
    if self.default_bridge is not None:
1475
      self.default_bridge = None
1476

    
1477
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1478
    # code can be removed once upgrading straight from 2.0 is deprecated.
1479
    if self.default_hypervisor is not None:
1480
      self.enabled_hypervisors = ([self.default_hypervisor] +
1481
        [hvname for hvname in self.enabled_hypervisors
1482
         if hvname != self.default_hypervisor])
1483
      self.default_hypervisor = None
1484

    
1485
    # maintain_node_health added after 2.1.1
1486
    if self.maintain_node_health is None:
1487
      self.maintain_node_health = False
1488

    
1489
    if self.uid_pool is None:
1490
      self.uid_pool = []
1491

    
1492
    if self.default_iallocator is None:
1493
      self.default_iallocator = ""
1494

    
1495
    # reserved_lvs added before 2.2
1496
    if self.reserved_lvs is None:
1497
      self.reserved_lvs = []
1498

    
1499
    # hidden and blacklisted operating systems added before 2.2.1
1500
    if self.hidden_os is None:
1501
      self.hidden_os = []
1502

    
1503
    if self.blacklisted_os is None:
1504
      self.blacklisted_os = []
1505

    
1506
    # primary_ip_family added before 2.3
1507
    if self.primary_ip_family is None:
1508
      self.primary_ip_family = AF_INET
1509

    
1510
    if self.master_netmask is None:
1511
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1512
      self.master_netmask = ipcls.iplen
1513

    
1514
    if self.prealloc_wipe_disks is None:
1515
      self.prealloc_wipe_disks = False
1516

    
1517
    # shared_file_storage_dir added before 2.5
1518
    if self.shared_file_storage_dir is None:
1519
      self.shared_file_storage_dir = ""
1520

    
1521
    if self.use_external_mip_script is None:
1522
      self.use_external_mip_script = False
1523

    
1524
    self.diskparams = UpgradeDiskParams(self.diskparams)
1525

    
1526
    # instance policy added before 2.6
1527
    if self.ipolicy is None:
1528
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1529
    else:
1530
      # we can either make sure to upgrade the ipolicy always, or only
1531
      # do it in some corner cases (e.g. missing keys); note that this
1532
      # will break any removal of keys from the ipolicy dict
1533
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1534

    
1535
  @property
1536
  def primary_hypervisor(self):
1537
    """The first hypervisor is the primary.
1538

1539
    Useful, for example, for L{Node}'s hv/disk state.
1540

1541
    """
1542
    return self.enabled_hypervisors[0]
1543

    
1544
  def ToDict(self):
1545
    """Custom function for cluster.
1546

1547
    """
1548
    mydict = super(Cluster, self).ToDict()
1549
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1550
    return mydict
1551

    
1552
  @classmethod
1553
  def FromDict(cls, val):
1554
    """Custom function for cluster.
1555

1556
    """
1557
    obj = super(Cluster, cls).FromDict(val)
1558
    if not isinstance(obj.tcpudp_port_pool, set):
1559
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1560
    return obj
1561

    
1562
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1563
    """Get the default hypervisor parameters for the cluster.
1564

1565
    @param hypervisor: the hypervisor name
1566
    @param os_name: if specified, we'll also update the defaults for this OS
1567
    @param skip_keys: if passed, list of keys not to use
1568
    @return: the defaults dict
1569

1570
    """
1571
    if skip_keys is None:
1572
      skip_keys = []
1573

    
1574
    fill_stack = [self.hvparams.get(hypervisor, {})]
1575
    if os_name is not None:
1576
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1577
      fill_stack.append(os_hvp)
1578

    
1579
    ret_dict = {}
1580
    for o_dict in fill_stack:
1581
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1582

    
1583
    return ret_dict
1584

    
1585
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1586
    """Fill a given hvparams dict with cluster defaults.
1587

1588
    @type hv_name: string
1589
    @param hv_name: the hypervisor to use
1590
    @type os_name: string
1591
    @param os_name: the OS to use for overriding the hypervisor defaults
1592
    @type skip_globals: boolean
1593
    @param skip_globals: if True, the global hypervisor parameters will
1594
        not be filled
1595
    @rtype: dict
1596
    @return: a copy of the given hvparams with missing keys filled from
1597
        the cluster defaults
1598

1599
    """
1600
    if skip_globals:
1601
      skip_keys = constants.HVC_GLOBALS
1602
    else:
1603
      skip_keys = []
1604

    
1605
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1606
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1607

    
1608
  def FillHV(self, instance, skip_globals=False):
1609
    """Fill an instance's hvparams dict with cluster defaults.
1610

1611
    @type instance: L{objects.Instance}
1612
    @param instance: the instance parameter to fill
1613
    @type skip_globals: boolean
1614
    @param skip_globals: if True, the global hypervisor parameters will
1615
        not be filled
1616
    @rtype: dict
1617
    @return: a copy of the instance's hvparams with missing keys filled from
1618
        the cluster defaults
1619

1620
    """
1621
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1622
                             instance.hvparams, skip_globals)
1623

    
1624
  def SimpleFillBE(self, beparams):
1625
    """Fill a given beparams dict with cluster defaults.
1626

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

1633
    """
1634
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1635

    
1636
  def FillBE(self, instance):
1637
    """Fill an instance's beparams dict with cluster defaults.
1638

1639
    @type instance: L{objects.Instance}
1640
    @param instance: the instance parameter to fill
1641
    @rtype: dict
1642
    @return: a copy of the instance's beparams with missing keys filled from
1643
        the cluster defaults
1644

1645
    """
1646
    return self.SimpleFillBE(instance.beparams)
1647

    
1648
  def SimpleFillNIC(self, nicparams):
1649
    """Fill a given nicparams dict with cluster defaults.
1650

1651
    @type nicparams: dict
1652
    @param nicparams: the dict to fill
1653
    @rtype: dict
1654
    @return: a copy of the passed in nicparams with missing keys filled
1655
        from the cluster defaults
1656

1657
    """
1658
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1659

    
1660
  def SimpleFillOS(self, os_name, os_params):
1661
    """Fill an instance's osparams dict with cluster defaults.
1662

1663
    @type os_name: string
1664
    @param os_name: the OS name to use
1665
    @type os_params: dict
1666
    @param os_params: the dict to fill with default values
1667
    @rtype: dict
1668
    @return: a copy of the instance's osparams with missing keys filled from
1669
        the cluster defaults
1670

1671
    """
1672
    name_only = os_name.split("+", 1)[0]
1673
    # base OS
1674
    result = self.osparams.get(name_only, {})
1675
    # OS with variant
1676
    result = FillDict(result, self.osparams.get(os_name, {}))
1677
    # specified params
1678
    return FillDict(result, os_params)
1679

    
1680
  @staticmethod
1681
  def SimpleFillHvState(hv_state):
1682
    """Fill an hv_state sub dict with cluster defaults.
1683

1684
    """
1685
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1686

    
1687
  @staticmethod
1688
  def SimpleFillDiskState(disk_state):
1689
    """Fill an disk_state sub dict with cluster defaults.
1690

1691
    """
1692
    return FillDict(constants.DS_DEFAULTS, disk_state)
1693

    
1694
  def FillND(self, node, nodegroup):
1695
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1696

1697
    @type node: L{objects.Node}
1698
    @param node: A Node object to fill
1699
    @type nodegroup: L{objects.NodeGroup}
1700
    @param nodegroup: A Node object to fill
1701
    @return a copy of the node's ndparams with defaults filled
1702

1703
    """
1704
    return self.SimpleFillND(nodegroup.FillND(node))
1705

    
1706
  def SimpleFillND(self, ndparams):
1707
    """Fill a given ndparams dict with defaults.
1708

1709
    @type ndparams: dict
1710
    @param ndparams: the dict to fill
1711
    @rtype: dict
1712
    @return: a copy of the passed in ndparams with missing keys filled
1713
        from the cluster defaults
1714

1715
    """
1716
    return FillDict(self.ndparams, ndparams)
1717

    
1718
  def SimpleFillIPolicy(self, ipolicy):
1719
    """ Fill instance policy dict with defaults.
1720

1721
    @type ipolicy: dict
1722
    @param ipolicy: the dict to fill
1723
    @rtype: dict
1724
    @return: a copy of passed ipolicy with missing keys filled from
1725
      the cluster defaults
1726

1727
    """
1728
    return FillIPolicy(self.ipolicy, ipolicy)
1729

    
1730

    
1731
class BlockDevStatus(ConfigObject):
1732
  """Config object representing the status of a block device."""
1733
  __slots__ = [
1734
    "dev_path",
1735
    "major",
1736
    "minor",
1737
    "sync_percent",
1738
    "estimated_time",
1739
    "is_degraded",
1740
    "ldisk_status",
1741
    ]
1742

    
1743

    
1744
class ImportExportStatus(ConfigObject):
1745
  """Config object representing the status of an import or export."""
1746
  __slots__ = [
1747
    "recent_output",
1748
    "listen_port",
1749
    "connected",
1750
    "progress_mbytes",
1751
    "progress_throughput",
1752
    "progress_eta",
1753
    "progress_percent",
1754
    "exit_status",
1755
    "error_message",
1756
    ] + _TIMESTAMPS
1757

    
1758

    
1759
class ImportExportOptions(ConfigObject):
1760
  """Options for import/export daemon
1761

1762
  @ivar key_name: X509 key name (None for cluster certificate)
1763
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1764
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1765
  @ivar magic: Used to ensure the connection goes to the right disk
1766
  @ivar ipv6: Whether to use IPv6
1767
  @ivar connect_timeout: Number of seconds for establishing connection
1768

1769
  """
1770
  __slots__ = [
1771
    "key_name",
1772
    "ca_pem",
1773
    "compress",
1774
    "magic",
1775
    "ipv6",
1776
    "connect_timeout",
1777
    ]
1778

    
1779

    
1780
class ConfdRequest(ConfigObject):
1781
  """Object holding a confd request.
1782

1783
  @ivar protocol: confd protocol version
1784
  @ivar type: confd query type
1785
  @ivar query: query request
1786
  @ivar rsalt: requested reply salt
1787

1788
  """
1789
  __slots__ = [
1790
    "protocol",
1791
    "type",
1792
    "query",
1793
    "rsalt",
1794
    ]
1795

    
1796

    
1797
class ConfdReply(ConfigObject):
1798
  """Object holding a confd reply.
1799

1800
  @ivar protocol: confd protocol version
1801
  @ivar status: reply status code (ok, error)
1802
  @ivar answer: confd query reply
1803
  @ivar serial: configuration serial number
1804

1805
  """
1806
  __slots__ = [
1807
    "protocol",
1808
    "status",
1809
    "answer",
1810
    "serial",
1811
    ]
1812

    
1813

    
1814
class QueryFieldDefinition(ConfigObject):
1815
  """Object holding a query field definition.
1816

1817
  @ivar name: Field name
1818
  @ivar title: Human-readable title
1819
  @ivar kind: Field type
1820
  @ivar doc: Human-readable description
1821

1822
  """
1823
  __slots__ = [
1824
    "name",
1825
    "title",
1826
    "kind",
1827
    "doc",
1828
    ]
1829

    
1830

    
1831
class _QueryResponseBase(ConfigObject):
1832
  __slots__ = [
1833
    "fields",
1834
    ]
1835

    
1836
  def ToDict(self):
1837
    """Custom function for serializing.
1838

1839
    """
1840
    mydict = super(_QueryResponseBase, self).ToDict()
1841
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1842
    return mydict
1843

    
1844
  @classmethod
1845
  def FromDict(cls, val):
1846
    """Custom function for de-serializing.
1847

1848
    """
1849
    obj = super(_QueryResponseBase, cls).FromDict(val)
1850
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1851
    return obj
1852

    
1853

    
1854
class QueryRequest(ConfigObject):
1855
  """Object holding a query request.
1856

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

    
1864

    
1865
class QueryResponse(_QueryResponseBase):
1866
  """Object holding the response to a query.
1867

1868
  @ivar fields: List of L{QueryFieldDefinition} objects
1869
  @ivar data: Requested data
1870

1871
  """
1872
  __slots__ = [
1873
    "data",
1874
    ]
1875

    
1876

    
1877
class QueryFieldsRequest(ConfigObject):
1878
  """Object holding a request for querying available fields.
1879

1880
  """
1881
  __slots__ = [
1882
    "what",
1883
    "fields",
1884
    ]
1885

    
1886

    
1887
class QueryFieldsResponse(_QueryResponseBase):
1888
  """Object holding the response to a query for fields.
1889

1890
  @ivar fields: List of L{QueryFieldDefinition} objects
1891

1892
  """
1893
  __slots__ = [
1894
    ]
1895

    
1896

    
1897
class MigrationStatus(ConfigObject):
1898
  """Object holding the status of a migration.
1899

1900
  """
1901
  __slots__ = [
1902
    "status",
1903
    "transferred_ram",
1904
    "total_ram",
1905
    ]
1906

    
1907

    
1908
class InstanceConsole(ConfigObject):
1909
  """Object describing how to access the console of an instance.
1910

1911
  """
1912
  __slots__ = [
1913
    "instance",
1914
    "kind",
1915
    "message",
1916
    "host",
1917
    "port",
1918
    "user",
1919
    "command",
1920
    "display",
1921
    ]
1922

    
1923
  def Validate(self):
1924
    """Validates contents of this object.
1925

1926
    """
1927
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1928
    assert self.instance, "Missing instance name"
1929
    assert self.message or self.kind in [constants.CONS_SSH,
1930
                                         constants.CONS_SPICE,
1931
                                         constants.CONS_VNC]
1932
    assert self.host or self.kind == constants.CONS_MESSAGE
1933
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1934
                                      constants.CONS_SSH]
1935
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1936
                                      constants.CONS_SPICE,
1937
                                      constants.CONS_VNC]
1938
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1939
                                         constants.CONS_SPICE,
1940
                                         constants.CONS_VNC]
1941
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1942
                                         constants.CONS_SPICE,
1943
                                         constants.CONS_SSH]
1944
    return True
1945

    
1946

    
1947
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1948
  """Simple wrapper over ConfigParse that allows serialization.
1949

1950
  This class is basically ConfigParser.SafeConfigParser with two
1951
  additional methods that allow it to serialize/unserialize to/from a
1952
  buffer.
1953

1954
  """
1955
  def Dumps(self):
1956
    """Dump this instance and return the string representation."""
1957
    buf = StringIO()
1958
    self.write(buf)
1959
    return buf.getvalue()
1960

    
1961
  @classmethod
1962
  def Loads(cls, data):
1963
    """Load data from a string."""
1964
    buf = StringIO(data)
1965
    cfp = cls()
1966
    cfp.readfp(buf)
1967
    return cfp