Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ ffa339ca

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_ISPECS:
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

897

898
  Note that this object is not actually used in the config, it's just
899
  used as a placeholder for a few functions.
900

901
  """
902
  @classmethod
903
  def CheckParameterSyntax(cls, ipolicy):
904
    """ Check the instance policy for validity.
905

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

    
916
  @classmethod
917
  def CheckISpecSyntax(cls, ipolicy, name):
918
    """Check the instance policy for validity on a given key.
919

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

923
    @type ipolicy: dict
924
    @param ipolicy: dictionary with min, max, std specs
925
    @type name: string
926
    @param name: what are the limits for
927
    @raise errors.ConfigureError: when specs for given name are not valid
928

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

    
941
  @classmethod
942
  def CheckDiskTemplates(cls, disk_templates):
943
    """Checks the disk templates for validity.
944

945
    """
946
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
947
    if wrong:
948
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
949
                                      utils.CommaJoin(wrong))
950

    
951

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

    
970
  def _ComputeSecondaryNodes(self):
971
    """Compute the list of secondary nodes.
972

973
    This is a simple wrapper over _ComputeAllNodes.
974

975
    """
976
    all_nodes = set(self._ComputeAllNodes())
977
    all_nodes.discard(self.primary_node)
978
    return tuple(all_nodes)
979

    
980
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
981
                             "List of secondary nodes")
982

    
983
  def _ComputeAllNodes(self):
984
    """Compute the list of all nodes.
985

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

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

    
1002
    all_nodes = set()
1003
    all_nodes.add(self.primary_node)
1004
    for device in self.disks:
1005
      _Helper(all_nodes, device)
1006
    return tuple(all_nodes)
1007

    
1008
  all_nodes = property(_ComputeAllNodes, None, None,
1009
                       "List of all nodes of the instance")
1010

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

1014
    This function figures out what logical volumes should belong on
1015
    which nodes, recursing through a device tree.
1016

1017
    @param lvmap: optional dictionary to receive the
1018
        'node' : ['lv', ...] data.
1019

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

1025
    """
1026
    if node == None:
1027
      node = self.primary_node
1028

    
1029
    if lvmap is None:
1030
      lvmap = {
1031
        node: [],
1032
        }
1033
      ret = lvmap
1034
    else:
1035
      if not node in lvmap:
1036
        lvmap[node] = []
1037
      ret = None
1038

    
1039
    if not devs:
1040
      devs = self.disks
1041

    
1042
    for dev in devs:
1043
      if dev.dev_type == constants.LD_LV:
1044
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1045

    
1046
      elif dev.dev_type in constants.LDS_DRBD:
1047
        if dev.children:
1048
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1049
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1050

    
1051
      elif dev.children:
1052
        self.MapLVsByNode(lvmap, dev.children, node)
1053

    
1054
    return ret
1055

    
1056
  def FindDisk(self, idx):
1057
    """Find a disk given having a specified index.
1058

1059
    This is just a wrapper that does validation of the index.
1060

1061
    @type idx: int
1062
    @param idx: the disk index
1063
    @rtype: L{Disk}
1064
    @return: the corresponding disk
1065
    @raise errors.OpPrereqError: when the given index is not valid
1066

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

    
1079
  def ToDict(self):
1080
    """Instance-specific conversion to standard python types.
1081

1082
    This replaces the children lists of objects with lists of standard
1083
    python types.
1084

1085
    """
1086
    bo = super(Instance, self).ToDict()
1087

    
1088
    for attr in "nics", "disks":
1089
      alist = bo.get(attr, None)
1090
      if alist:
1091
        nlist = self._ContainerToDicts(alist)
1092
      else:
1093
        nlist = []
1094
      bo[attr] = nlist
1095
    return bo
1096

    
1097
  @classmethod
1098
  def FromDict(cls, val):
1099
    """Custom function for instances.
1100

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

    
1114
  def UpgradeConfig(self):
1115
    """Fill defaults for missing configuration values.
1116

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

    
1132

    
1133
class OS(ConfigObject):
1134
  """Config object representing an operating system.
1135

1136
  @type supported_parameters: list
1137
  @ivar supported_parameters: a list of tuples, name and description,
1138
      containing the supported parameters by this OS
1139

1140
  @type VARIANT_DELIM: string
1141
  @cvar VARIANT_DELIM: the variant delimiter
1142

1143
  """
1144
  __slots__ = [
1145
    "name",
1146
    "path",
1147
    "api_versions",
1148
    "create_script",
1149
    "export_script",
1150
    "import_script",
1151
    "rename_script",
1152
    "verify_script",
1153
    "supported_variants",
1154
    "supported_parameters",
1155
    ]
1156

    
1157
  VARIANT_DELIM = "+"
1158

    
1159
  @classmethod
1160
  def SplitNameVariant(cls, name):
1161
    """Splits the name into the proper name and variant.
1162

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

1168
    """
1169
    nv = name.split(cls.VARIANT_DELIM, 1)
1170
    if len(nv) == 1:
1171
      nv.append("")
1172
    return nv
1173

    
1174
  @classmethod
1175
  def GetName(cls, name):
1176
    """Returns the proper name of the os (without the variant).
1177

1178
    @param name: the OS (unprocessed) name
1179

1180
    """
1181
    return cls.SplitNameVariant(name)[0]
1182

    
1183
  @classmethod
1184
  def GetVariant(cls, name):
1185
    """Returns the variant the os (without the base name).
1186

1187
    @param name: the OS (unprocessed) name
1188

1189
    """
1190
    return cls.SplitNameVariant(name)[1]
1191

    
1192

    
1193
class NodeHvState(ConfigObject):
1194
  """Hypvervisor state on a node.
1195

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

1205
  """
1206
  __slots__ = [
1207
    "mem_total",
1208
    "mem_node",
1209
    "mem_hv",
1210
    "mem_inst",
1211
    "cpu_total",
1212
    "cpu_node",
1213
    ] + _TIMESTAMPS
1214

    
1215

    
1216
class NodeDiskState(ConfigObject):
1217
  """Disk state on a node.
1218

1219
  """
1220
  __slots__ = [
1221
    "total",
1222
    "reserved",
1223
    "overhead",
1224
    ] + _TIMESTAMPS
1225

    
1226

    
1227
class Node(TaggableObject):
1228
  """Config object representing a node.
1229

1230
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1231
  @ivar hv_state_static: Hypervisor state overriden by user
1232
  @ivar disk_state: Disk state (e.g. free space)
1233
  @ivar disk_state_static: Disk state overriden by user
1234

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

    
1255
  def UpgradeConfig(self):
1256
    """Fill defaults for missing configuration values.
1257

1258
    """
1259
    # pylint: disable=E0203
1260
    # because these are "defined" via slots, not manually
1261
    if self.master_capable is None:
1262
      self.master_capable = True
1263

    
1264
    if self.vm_capable is None:
1265
      self.vm_capable = True
1266

    
1267
    if self.ndparams is None:
1268
      self.ndparams = {}
1269

    
1270
    if self.powered is None:
1271
      self.powered = True
1272

    
1273
  def ToDict(self):
1274
    """Custom function for serializing.
1275

1276
    """
1277
    data = super(Node, self).ToDict()
1278

    
1279
    hv_state = data.get("hv_state", None)
1280
    if hv_state is not None:
1281
      data["hv_state"] = self._ContainerToDicts(hv_state)
1282

    
1283
    disk_state = data.get("disk_state", None)
1284
    if disk_state is not None:
1285
      data["disk_state"] = \
1286
        dict((key, self._ContainerToDicts(value))
1287
             for (key, value) in disk_state.items())
1288

    
1289
    return data
1290

    
1291
  @classmethod
1292
  def FromDict(cls, val):
1293
    """Custom function for deserializing.
1294

1295
    """
1296
    obj = super(Node, cls).FromDict(val)
1297

    
1298
    if obj.hv_state is not None:
1299
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1300

    
1301
    if obj.disk_state is not None:
1302
      obj.disk_state = \
1303
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1304
             for (key, value) in obj.disk_state.items())
1305

    
1306
    return obj
1307

    
1308

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

    
1323
  def ToDict(self):
1324
    """Custom function for nodegroup.
1325

1326
    This discards the members object, which gets recalculated and is only kept
1327
    in memory.
1328

1329
    """
1330
    mydict = super(NodeGroup, self).ToDict()
1331
    del mydict["members"]
1332
    return mydict
1333

    
1334
  @classmethod
1335
  def FromDict(cls, val):
1336
    """Custom function for nodegroup.
1337

1338
    The members slot is initialized to an empty list, upon deserialization.
1339

1340
    """
1341
    obj = super(NodeGroup, cls).FromDict(val)
1342
    obj.members = []
1343
    return obj
1344

    
1345
  def UpgradeConfig(self):
1346
    """Fill defaults for missing configuration values.
1347

1348
    """
1349
    if self.ndparams is None:
1350
      self.ndparams = {}
1351

    
1352
    if self.serial_no is None:
1353
      self.serial_no = 1
1354

    
1355
    if self.alloc_policy is None:
1356
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1357

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

    
1363
    self.diskparams = UpgradeDiskParams(self.diskparams)
1364
    if self.ipolicy is None:
1365
      self.ipolicy = MakeEmptyIPolicy()
1366

    
1367
  def FillND(self, node):
1368
    """Return filled out ndparams for L{objects.Node}
1369

1370
    @type node: L{objects.Node}
1371
    @param node: A Node object to fill
1372
    @return a copy of the node's ndparams with defaults filled
1373

1374
    """
1375
    return self.SimpleFillND(node.ndparams)
1376

    
1377
  def SimpleFillND(self, ndparams):
1378
    """Fill a given ndparams dict with defaults.
1379

1380
    @type ndparams: dict
1381
    @param ndparams: the dict to fill
1382
    @rtype: dict
1383
    @return: a copy of the passed in ndparams with missing keys filled
1384
        from the node group defaults
1385

1386
    """
1387
    return FillDict(self.ndparams, ndparams)
1388

    
1389

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

    
1434
  def UpgradeConfig(self):
1435
    """Fill defaults for missing configuration values.
1436

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

    
1447
    if self.os_hvp is None:
1448
      self.os_hvp = {}
1449

    
1450
    # osparams added before 2.2
1451
    if self.osparams is None:
1452
      self.osparams = {}
1453

    
1454
    if self.ndparams is None:
1455
      self.ndparams = constants.NDC_DEFAULTS
1456

    
1457
    self.beparams = UpgradeGroupedParams(self.beparams,
1458
                                         constants.BEC_DEFAULTS)
1459
    for beparams_group in self.beparams:
1460
      UpgradeBeParams(self.beparams[beparams_group])
1461

    
1462
    migrate_default_bridge = not self.nicparams
1463
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1464
                                          constants.NICC_DEFAULTS)
1465
    if migrate_default_bridge:
1466
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1467
        self.default_bridge
1468

    
1469
    if self.modify_etc_hosts is None:
1470
      self.modify_etc_hosts = True
1471

    
1472
    if self.modify_ssh_setup is None:
1473
      self.modify_ssh_setup = True
1474

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

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

    
1489
    # maintain_node_health added after 2.1.1
1490
    if self.maintain_node_health is None:
1491
      self.maintain_node_health = False
1492

    
1493
    if self.uid_pool is None:
1494
      self.uid_pool = []
1495

    
1496
    if self.default_iallocator is None:
1497
      self.default_iallocator = ""
1498

    
1499
    # reserved_lvs added before 2.2
1500
    if self.reserved_lvs is None:
1501
      self.reserved_lvs = []
1502

    
1503
    # hidden and blacklisted operating systems added before 2.2.1
1504
    if self.hidden_os is None:
1505
      self.hidden_os = []
1506

    
1507
    if self.blacklisted_os is None:
1508
      self.blacklisted_os = []
1509

    
1510
    # primary_ip_family added before 2.3
1511
    if self.primary_ip_family is None:
1512
      self.primary_ip_family = AF_INET
1513

    
1514
    if self.master_netmask is None:
1515
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1516
      self.master_netmask = ipcls.iplen
1517

    
1518
    if self.prealloc_wipe_disks is None:
1519
      self.prealloc_wipe_disks = False
1520

    
1521
    # shared_file_storage_dir added before 2.5
1522
    if self.shared_file_storage_dir is None:
1523
      self.shared_file_storage_dir = ""
1524

    
1525
    if self.use_external_mip_script is None:
1526
      self.use_external_mip_script = False
1527

    
1528
    self.diskparams = UpgradeDiskParams(self.diskparams)
1529

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

    
1539
  @property
1540
  def primary_hypervisor(self):
1541
    """The first hypervisor is the primary.
1542

1543
    Useful, for example, for L{Node}'s hv/disk state.
1544

1545
    """
1546
    return self.enabled_hypervisors[0]
1547

    
1548
  def ToDict(self):
1549
    """Custom function for cluster.
1550

1551
    """
1552
    mydict = super(Cluster, self).ToDict()
1553
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1554
    return mydict
1555

    
1556
  @classmethod
1557
  def FromDict(cls, val):
1558
    """Custom function for cluster.
1559

1560
    """
1561
    obj = super(Cluster, cls).FromDict(val)
1562
    if not isinstance(obj.tcpudp_port_pool, set):
1563
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1564
    return obj
1565

    
1566
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1567
    """Get the default hypervisor parameters for the cluster.
1568

1569
    @param hypervisor: the hypervisor name
1570
    @param os_name: if specified, we'll also update the defaults for this OS
1571
    @param skip_keys: if passed, list of keys not to use
1572
    @return: the defaults dict
1573

1574
    """
1575
    if skip_keys is None:
1576
      skip_keys = []
1577

    
1578
    fill_stack = [self.hvparams.get(hypervisor, {})]
1579
    if os_name is not None:
1580
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1581
      fill_stack.append(os_hvp)
1582

    
1583
    ret_dict = {}
1584
    for o_dict in fill_stack:
1585
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1586

    
1587
    return ret_dict
1588

    
1589
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1590
    """Fill a given hvparams dict with cluster defaults.
1591

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

1603
    """
1604
    if skip_globals:
1605
      skip_keys = constants.HVC_GLOBALS
1606
    else:
1607
      skip_keys = []
1608

    
1609
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1610
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1611

    
1612
  def FillHV(self, instance, skip_globals=False):
1613
    """Fill an instance's hvparams dict with cluster defaults.
1614

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

1624
    """
1625
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1626
                             instance.hvparams, skip_globals)
1627

    
1628
  def SimpleFillBE(self, beparams):
1629
    """Fill a given beparams dict with cluster defaults.
1630

1631
    @type beparams: dict
1632
    @param beparams: the dict to fill
1633
    @rtype: dict
1634
    @return: a copy of the passed in beparams with missing keys filled
1635
        from the cluster defaults
1636

1637
    """
1638
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1639

    
1640
  def FillBE(self, instance):
1641
    """Fill an instance's beparams dict with cluster defaults.
1642

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

1649
    """
1650
    return self.SimpleFillBE(instance.beparams)
1651

    
1652
  def SimpleFillNIC(self, nicparams):
1653
    """Fill a given nicparams dict with cluster defaults.
1654

1655
    @type nicparams: dict
1656
    @param nicparams: the dict to fill
1657
    @rtype: dict
1658
    @return: a copy of the passed in nicparams with missing keys filled
1659
        from the cluster defaults
1660

1661
    """
1662
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1663

    
1664
  def SimpleFillOS(self, os_name, os_params):
1665
    """Fill an instance's osparams dict with cluster defaults.
1666

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

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

    
1684
  @staticmethod
1685
  def SimpleFillHvState(hv_state):
1686
    """Fill an hv_state sub dict with cluster defaults.
1687

1688
    """
1689
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1690

    
1691
  @staticmethod
1692
  def SimpleFillDiskState(disk_state):
1693
    """Fill an disk_state sub dict with cluster defaults.
1694

1695
    """
1696
    return FillDict(constants.DS_DEFAULTS, disk_state)
1697

    
1698
  def FillND(self, node, nodegroup):
1699
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1700

1701
    @type node: L{objects.Node}
1702
    @param node: A Node object to fill
1703
    @type nodegroup: L{objects.NodeGroup}
1704
    @param nodegroup: A Node object to fill
1705
    @return a copy of the node's ndparams with defaults filled
1706

1707
    """
1708
    return self.SimpleFillND(nodegroup.FillND(node))
1709

    
1710
  def SimpleFillND(self, ndparams):
1711
    """Fill a given ndparams dict with defaults.
1712

1713
    @type ndparams: dict
1714
    @param ndparams: the dict to fill
1715
    @rtype: dict
1716
    @return: a copy of the passed in ndparams with missing keys filled
1717
        from the cluster defaults
1718

1719
    """
1720
    return FillDict(self.ndparams, ndparams)
1721

    
1722
  def SimpleFillIPolicy(self, ipolicy):
1723
    """ Fill instance policy dict with defaults.
1724

1725
    @type ipolicy: dict
1726
    @param ipolicy: the dict to fill
1727
    @rtype: dict
1728
    @return: a copy of passed ipolicy with missing keys filled from
1729
      the cluster defaults
1730

1731
    """
1732
    return FillIPolicy(self.ipolicy, ipolicy)
1733

    
1734

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

    
1747

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

    
1762

    
1763
class ImportExportOptions(ConfigObject):
1764
  """Options for import/export daemon
1765

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

1773
  """
1774
  __slots__ = [
1775
    "key_name",
1776
    "ca_pem",
1777
    "compress",
1778
    "magic",
1779
    "ipv6",
1780
    "connect_timeout",
1781
    ]
1782

    
1783

    
1784
class ConfdRequest(ConfigObject):
1785
  """Object holding a confd request.
1786

1787
  @ivar protocol: confd protocol version
1788
  @ivar type: confd query type
1789
  @ivar query: query request
1790
  @ivar rsalt: requested reply salt
1791

1792
  """
1793
  __slots__ = [
1794
    "protocol",
1795
    "type",
1796
    "query",
1797
    "rsalt",
1798
    ]
1799

    
1800

    
1801
class ConfdReply(ConfigObject):
1802
  """Object holding a confd reply.
1803

1804
  @ivar protocol: confd protocol version
1805
  @ivar status: reply status code (ok, error)
1806
  @ivar answer: confd query reply
1807
  @ivar serial: configuration serial number
1808

1809
  """
1810
  __slots__ = [
1811
    "protocol",
1812
    "status",
1813
    "answer",
1814
    "serial",
1815
    ]
1816

    
1817

    
1818
class QueryFieldDefinition(ConfigObject):
1819
  """Object holding a query field definition.
1820

1821
  @ivar name: Field name
1822
  @ivar title: Human-readable title
1823
  @ivar kind: Field type
1824
  @ivar doc: Human-readable description
1825

1826
  """
1827
  __slots__ = [
1828
    "name",
1829
    "title",
1830
    "kind",
1831
    "doc",
1832
    ]
1833

    
1834

    
1835
class _QueryResponseBase(ConfigObject):
1836
  __slots__ = [
1837
    "fields",
1838
    ]
1839

    
1840
  def ToDict(self):
1841
    """Custom function for serializing.
1842

1843
    """
1844
    mydict = super(_QueryResponseBase, self).ToDict()
1845
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1846
    return mydict
1847

    
1848
  @classmethod
1849
  def FromDict(cls, val):
1850
    """Custom function for de-serializing.
1851

1852
    """
1853
    obj = super(_QueryResponseBase, cls).FromDict(val)
1854
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1855
    return obj
1856

    
1857

    
1858
class QueryRequest(ConfigObject):
1859
  """Object holding a query request.
1860

1861
  """
1862
  __slots__ = [
1863
    "what",
1864
    "fields",
1865
    "qfilter",
1866
    ]
1867

    
1868

    
1869
class QueryResponse(_QueryResponseBase):
1870
  """Object holding the response to a query.
1871

1872
  @ivar fields: List of L{QueryFieldDefinition} objects
1873
  @ivar data: Requested data
1874

1875
  """
1876
  __slots__ = [
1877
    "data",
1878
    ]
1879

    
1880

    
1881
class QueryFieldsRequest(ConfigObject):
1882
  """Object holding a request for querying available fields.
1883

1884
  """
1885
  __slots__ = [
1886
    "what",
1887
    "fields",
1888
    ]
1889

    
1890

    
1891
class QueryFieldsResponse(_QueryResponseBase):
1892
  """Object holding the response to a query for fields.
1893

1894
  @ivar fields: List of L{QueryFieldDefinition} objects
1895

1896
  """
1897
  __slots__ = [
1898
    ]
1899

    
1900

    
1901
class MigrationStatus(ConfigObject):
1902
  """Object holding the status of a migration.
1903

1904
  """
1905
  __slots__ = [
1906
    "status",
1907
    "transferred_ram",
1908
    "total_ram",
1909
    ]
1910

    
1911

    
1912
class InstanceConsole(ConfigObject):
1913
  """Object describing how to access the console of an instance.
1914

1915
  """
1916
  __slots__ = [
1917
    "instance",
1918
    "kind",
1919
    "message",
1920
    "host",
1921
    "port",
1922
    "user",
1923
    "command",
1924
    "display",
1925
    ]
1926

    
1927
  def Validate(self):
1928
    """Validates contents of this object.
1929

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

    
1950

    
1951
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1952
  """Simple wrapper over ConfigParse that allows serialization.
1953

1954
  This class is basically ConfigParser.SafeConfigParser with two
1955
  additional methods that allow it to serialize/unserialize to/from a
1956
  buffer.
1957

1958
  """
1959
  def Dumps(self):
1960
    """Dump this instance and return the string representation."""
1961
    buf = StringIO()
1962
    self.write(buf)
1963
    return buf.getvalue()
1964

    
1965
  @classmethod
1966
  def Loads(cls, data):
1967
    """Load data from a string."""
1968
    buf = StringIO(data)
1969
    cfp = cls()
1970
    cfp.readfp(buf)
1971
    return cfp