Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ d04c9d45

History | View | Annotate | Download (55.1 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 UnsetSize(self):
734
    """Sets recursively the size to zero for the disk and its children.
735

736
    """
737
    if self.children:
738
      for child in self.children:
739
        child.UnsetSize()
740
    self.size = 0
741

    
742
  def SetPhysicalID(self, target_node, nodes_ip):
743
    """Convert the logical ID to the physical ID.
744

745
    This is used only for drbd, which needs ip/port configuration.
746

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

751
    Arguments:
752
      - target_node: the node we wish to configure for
753
      - nodes_ip: a mapping of node name to ip
754

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

759
    """
760
    if self.children:
761
      for child in self.children:
762
        child.SetPhysicalID(target_node, nodes_ip)
763

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

    
786
  def ToDict(self):
787
    """Disk-specific conversion to standard python types.
788

789
    This replaces the children lists of objects with lists of
790
    standard python types.
791

792
    """
793
    bo = super(Disk, self).ToDict()
794

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

    
801
  @classmethod
802
  def FromDict(cls, val):
803
    """Custom function for Disks
804

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

    
819
  def __str__(self):
820
    """Custom str() formatter for disks.
821

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

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

    
854
  def Verify(self):
855
    """Checks that this disk is correctly configured.
856

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

    
863
  def UpgradeConfig(self):
864
    """Fill defaults for missing configuration values.
865

866
    """
867
    if self.children:
868
      for child in self.children:
869
        child.UpgradeConfig()
870

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

    
878

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

    
883
  @classmethod
884
  def CheckParameterSyntax(cls, ipolicy):
885
    """ Check the instance policy for validity.
886

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

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

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

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

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

    
922
  @classmethod
923
  def CheckDiskTemplates(cls, disk_templates):
924
    """Checks the disk templates for validity.
925

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

    
932

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

    
951
  def _ComputeSecondaryNodes(self):
952
    """Compute the list of secondary nodes.
953

954
    This is a simple wrapper over _ComputeAllNodes.
955

956
    """
957
    all_nodes = set(self._ComputeAllNodes())
958
    all_nodes.discard(self.primary_node)
959
    return tuple(all_nodes)
960

    
961
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
962
                             "List of secondary nodes")
963

    
964
  def _ComputeAllNodes(self):
965
    """Compute the list of all nodes.
966

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

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

    
983
    all_nodes = set()
984
    all_nodes.add(self.primary_node)
985
    for device in self.disks:
986
      _Helper(all_nodes, device)
987
    return tuple(all_nodes)
988

    
989
  all_nodes = property(_ComputeAllNodes, None, None,
990
                       "List of all nodes of the instance")
991

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

995
    This function figures out what logical volumes should belong on
996
    which nodes, recursing through a device tree.
997

998
    @param lvmap: optional dictionary to receive the
999
        'node' : ['lv', ...] data.
1000

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

1006
    """
1007
    if node == None:
1008
      node = self.primary_node
1009

    
1010
    if lvmap is None:
1011
      lvmap = {
1012
        node: [],
1013
        }
1014
      ret = lvmap
1015
    else:
1016
      if not node in lvmap:
1017
        lvmap[node] = []
1018
      ret = None
1019

    
1020
    if not devs:
1021
      devs = self.disks
1022

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

    
1027
      elif dev.dev_type in constants.LDS_DRBD:
1028
        if dev.children:
1029
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1030
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1031

    
1032
      elif dev.children:
1033
        self.MapLVsByNode(lvmap, dev.children, node)
1034

    
1035
    return ret
1036

    
1037
  def FindDisk(self, idx):
1038
    """Find a disk given having a specified index.
1039

1040
    This is just a wrapper that does validation of the index.
1041

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

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

    
1060
  def ToDict(self):
1061
    """Instance-specific conversion to standard python types.
1062

1063
    This replaces the children lists of objects with lists of standard
1064
    python types.
1065

1066
    """
1067
    bo = super(Instance, self).ToDict()
1068

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

    
1078
  @classmethod
1079
  def FromDict(cls, val):
1080
    """Custom function for instances.
1081

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

    
1095
  def UpgradeConfig(self):
1096
    """Fill defaults for missing configuration values.
1097

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

    
1113

    
1114
class OS(ConfigObject):
1115
  """Config object representing an operating system.
1116

1117
  @type supported_parameters: list
1118
  @ivar supported_parameters: a list of tuples, name and description,
1119
      containing the supported parameters by this OS
1120

1121
  @type VARIANT_DELIM: string
1122
  @cvar VARIANT_DELIM: the variant delimiter
1123

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

    
1138
  VARIANT_DELIM = "+"
1139

    
1140
  @classmethod
1141
  def SplitNameVariant(cls, name):
1142
    """Splits the name into the proper name and variant.
1143

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

1149
    """
1150
    nv = name.split(cls.VARIANT_DELIM, 1)
1151
    if len(nv) == 1:
1152
      nv.append("")
1153
    return nv
1154

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

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

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

    
1164
  @classmethod
1165
  def GetVariant(cls, name):
1166
    """Returns the variant the os (without the base name).
1167

1168
    @param name: the OS (unprocessed) name
1169

1170
    """
1171
    return cls.SplitNameVariant(name)[1]
1172

    
1173

    
1174
class NodeHvState(ConfigObject):
1175
  """Hypvervisor state on a node.
1176

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

1186
  """
1187
  __slots__ = [
1188
    "mem_total",
1189
    "mem_node",
1190
    "mem_hv",
1191
    "mem_inst",
1192
    "cpu_total",
1193
    "cpu_node",
1194
    ] + _TIMESTAMPS
1195

    
1196

    
1197
class NodeDiskState(ConfigObject):
1198
  """Disk state on a node.
1199

1200
  """
1201
  __slots__ = [
1202
    "total",
1203
    "reserved",
1204
    "overhead",
1205
    ] + _TIMESTAMPS
1206

    
1207

    
1208
class Node(TaggableObject):
1209
  """Config object representing a node.
1210

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

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

    
1236
  def UpgradeConfig(self):
1237
    """Fill defaults for missing configuration values.
1238

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

    
1245
    if self.vm_capable is None:
1246
      self.vm_capable = True
1247

    
1248
    if self.ndparams is None:
1249
      self.ndparams = {}
1250

    
1251
    if self.powered is None:
1252
      self.powered = True
1253

    
1254
  def ToDict(self):
1255
    """Custom function for serializing.
1256

1257
    """
1258
    data = super(Node, self).ToDict()
1259

    
1260
    hv_state = data.get("hv_state", None)
1261
    if hv_state is not None:
1262
      data["hv_state"] = self._ContainerToDicts(hv_state)
1263

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

    
1270
    return data
1271

    
1272
  @classmethod
1273
  def FromDict(cls, val):
1274
    """Custom function for deserializing.
1275

1276
    """
1277
    obj = super(Node, cls).FromDict(val)
1278

    
1279
    if obj.hv_state is not None:
1280
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1281

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

    
1287
    return obj
1288

    
1289

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

    
1304
  def ToDict(self):
1305
    """Custom function for nodegroup.
1306

1307
    This discards the members object, which gets recalculated and is only kept
1308
    in memory.
1309

1310
    """
1311
    mydict = super(NodeGroup, self).ToDict()
1312
    del mydict["members"]
1313
    return mydict
1314

    
1315
  @classmethod
1316
  def FromDict(cls, val):
1317
    """Custom function for nodegroup.
1318

1319
    The members slot is initialized to an empty list, upon deserialization.
1320

1321
    """
1322
    obj = super(NodeGroup, cls).FromDict(val)
1323
    obj.members = []
1324
    return obj
1325

    
1326
  def UpgradeConfig(self):
1327
    """Fill defaults for missing configuration values.
1328

1329
    """
1330
    if self.ndparams is None:
1331
      self.ndparams = {}
1332

    
1333
    if self.serial_no is None:
1334
      self.serial_no = 1
1335

    
1336
    if self.alloc_policy is None:
1337
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1338

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

    
1344
    self.diskparams = UpgradeDiskParams(self.diskparams)
1345
    if self.ipolicy is None:
1346
      self.ipolicy = MakeEmptyIPolicy()
1347

    
1348
  def FillND(self, node):
1349
    """Return filled out ndparams for L{objects.Node}
1350

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

1355
    """
1356
    return self.SimpleFillND(node.ndparams)
1357

    
1358
  def SimpleFillND(self, ndparams):
1359
    """Fill a given ndparams dict with defaults.
1360

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

1367
    """
1368
    return FillDict(self.ndparams, ndparams)
1369

    
1370

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

    
1415
  def UpgradeConfig(self):
1416
    """Fill defaults for missing configuration values.
1417

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

    
1428
    if self.os_hvp is None:
1429
      self.os_hvp = {}
1430

    
1431
    # osparams added before 2.2
1432
    if self.osparams is None:
1433
      self.osparams = {}
1434

    
1435
    if self.ndparams is None:
1436
      self.ndparams = constants.NDC_DEFAULTS
1437

    
1438
    self.beparams = UpgradeGroupedParams(self.beparams,
1439
                                         constants.BEC_DEFAULTS)
1440
    for beparams_group in self.beparams:
1441
      UpgradeBeParams(self.beparams[beparams_group])
1442

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

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

    
1453
    if self.modify_ssh_setup is None:
1454
      self.modify_ssh_setup = True
1455

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

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

    
1470
    # maintain_node_health added after 2.1.1
1471
    if self.maintain_node_health is None:
1472
      self.maintain_node_health = False
1473

    
1474
    if self.uid_pool is None:
1475
      self.uid_pool = []
1476

    
1477
    if self.default_iallocator is None:
1478
      self.default_iallocator = ""
1479

    
1480
    # reserved_lvs added before 2.2
1481
    if self.reserved_lvs is None:
1482
      self.reserved_lvs = []
1483

    
1484
    # hidden and blacklisted operating systems added before 2.2.1
1485
    if self.hidden_os is None:
1486
      self.hidden_os = []
1487

    
1488
    if self.blacklisted_os is None:
1489
      self.blacklisted_os = []
1490

    
1491
    # primary_ip_family added before 2.3
1492
    if self.primary_ip_family is None:
1493
      self.primary_ip_family = AF_INET
1494

    
1495
    if self.master_netmask is None:
1496
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1497
      self.master_netmask = ipcls.iplen
1498

    
1499
    if self.prealloc_wipe_disks is None:
1500
      self.prealloc_wipe_disks = False
1501

    
1502
    # shared_file_storage_dir added before 2.5
1503
    if self.shared_file_storage_dir is None:
1504
      self.shared_file_storage_dir = ""
1505

    
1506
    if self.use_external_mip_script is None:
1507
      self.use_external_mip_script = False
1508

    
1509
    self.diskparams = UpgradeDiskParams(self.diskparams)
1510

    
1511
    # instance policy added before 2.6
1512
    if self.ipolicy is None:
1513
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1514
    else:
1515
      # we can either make sure to upgrade the ipolicy always, or only
1516
      # do it in some corner cases (e.g. missing keys); note that this
1517
      # will break any removal of keys from the ipolicy dict
1518
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1519

    
1520
  @property
1521
  def primary_hypervisor(self):
1522
    """The first hypervisor is the primary.
1523

1524
    Useful, for example, for L{Node}'s hv/disk state.
1525

1526
    """
1527
    return self.enabled_hypervisors[0]
1528

    
1529
  def ToDict(self):
1530
    """Custom function for cluster.
1531

1532
    """
1533
    mydict = super(Cluster, self).ToDict()
1534
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1535
    return mydict
1536

    
1537
  @classmethod
1538
  def FromDict(cls, val):
1539
    """Custom function for cluster.
1540

1541
    """
1542
    obj = super(Cluster, cls).FromDict(val)
1543
    if not isinstance(obj.tcpudp_port_pool, set):
1544
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1545
    return obj
1546

    
1547
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1548
    """Get the default hypervisor parameters for the cluster.
1549

1550
    @param hypervisor: the hypervisor name
1551
    @param os_name: if specified, we'll also update the defaults for this OS
1552
    @param skip_keys: if passed, list of keys not to use
1553
    @return: the defaults dict
1554

1555
    """
1556
    if skip_keys is None:
1557
      skip_keys = []
1558

    
1559
    fill_stack = [self.hvparams.get(hypervisor, {})]
1560
    if os_name is not None:
1561
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1562
      fill_stack.append(os_hvp)
1563

    
1564
    ret_dict = {}
1565
    for o_dict in fill_stack:
1566
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1567

    
1568
    return ret_dict
1569

    
1570
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1571
    """Fill a given hvparams dict with cluster defaults.
1572

1573
    @type hv_name: string
1574
    @param hv_name: the hypervisor to use
1575
    @type os_name: string
1576
    @param os_name: the OS to use for overriding the hypervisor defaults
1577
    @type skip_globals: boolean
1578
    @param skip_globals: if True, the global hypervisor parameters will
1579
        not be filled
1580
    @rtype: dict
1581
    @return: a copy of the given hvparams with missing keys filled from
1582
        the cluster defaults
1583

1584
    """
1585
    if skip_globals:
1586
      skip_keys = constants.HVC_GLOBALS
1587
    else:
1588
      skip_keys = []
1589

    
1590
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1591
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1592

    
1593
  def FillHV(self, instance, skip_globals=False):
1594
    """Fill an instance's hvparams dict with cluster defaults.
1595

1596
    @type instance: L{objects.Instance}
1597
    @param instance: the instance parameter to fill
1598
    @type skip_globals: boolean
1599
    @param skip_globals: if True, the global hypervisor parameters will
1600
        not be filled
1601
    @rtype: dict
1602
    @return: a copy of the instance's hvparams with missing keys filled from
1603
        the cluster defaults
1604

1605
    """
1606
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1607
                             instance.hvparams, skip_globals)
1608

    
1609
  def SimpleFillBE(self, beparams):
1610
    """Fill a given beparams dict with cluster defaults.
1611

1612
    @type beparams: dict
1613
    @param beparams: the dict to fill
1614
    @rtype: dict
1615
    @return: a copy of the passed in beparams with missing keys filled
1616
        from the cluster defaults
1617

1618
    """
1619
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1620

    
1621
  def FillBE(self, instance):
1622
    """Fill an instance's beparams dict with cluster defaults.
1623

1624
    @type instance: L{objects.Instance}
1625
    @param instance: the instance parameter to fill
1626
    @rtype: dict
1627
    @return: a copy of the instance's beparams with missing keys filled from
1628
        the cluster defaults
1629

1630
    """
1631
    return self.SimpleFillBE(instance.beparams)
1632

    
1633
  def SimpleFillNIC(self, nicparams):
1634
    """Fill a given nicparams dict with cluster defaults.
1635

1636
    @type nicparams: dict
1637
    @param nicparams: the dict to fill
1638
    @rtype: dict
1639
    @return: a copy of the passed in nicparams with missing keys filled
1640
        from the cluster defaults
1641

1642
    """
1643
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1644

    
1645
  def SimpleFillOS(self, os_name, os_params):
1646
    """Fill an instance's osparams dict with cluster defaults.
1647

1648
    @type os_name: string
1649
    @param os_name: the OS name to use
1650
    @type os_params: dict
1651
    @param os_params: the dict to fill with default values
1652
    @rtype: dict
1653
    @return: a copy of the instance's osparams with missing keys filled from
1654
        the cluster defaults
1655

1656
    """
1657
    name_only = os_name.split("+", 1)[0]
1658
    # base OS
1659
    result = self.osparams.get(name_only, {})
1660
    # OS with variant
1661
    result = FillDict(result, self.osparams.get(os_name, {}))
1662
    # specified params
1663
    return FillDict(result, os_params)
1664

    
1665
  @staticmethod
1666
  def SimpleFillHvState(hv_state):
1667
    """Fill an hv_state sub dict with cluster defaults.
1668

1669
    """
1670
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1671

    
1672
  @staticmethod
1673
  def SimpleFillDiskState(disk_state):
1674
    """Fill an disk_state sub dict with cluster defaults.
1675

1676
    """
1677
    return FillDict(constants.DS_DEFAULTS, disk_state)
1678

    
1679
  def FillND(self, node, nodegroup):
1680
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1681

1682
    @type node: L{objects.Node}
1683
    @param node: A Node object to fill
1684
    @type nodegroup: L{objects.NodeGroup}
1685
    @param nodegroup: A Node object to fill
1686
    @return a copy of the node's ndparams with defaults filled
1687

1688
    """
1689
    return self.SimpleFillND(nodegroup.FillND(node))
1690

    
1691
  def SimpleFillND(self, ndparams):
1692
    """Fill a given ndparams dict with defaults.
1693

1694
    @type ndparams: dict
1695
    @param ndparams: the dict to fill
1696
    @rtype: dict
1697
    @return: a copy of the passed in ndparams with missing keys filled
1698
        from the cluster defaults
1699

1700
    """
1701
    return FillDict(self.ndparams, ndparams)
1702

    
1703
  def SimpleFillIPolicy(self, ipolicy):
1704
    """ Fill instance policy dict with defaults.
1705

1706
    @type ipolicy: dict
1707
    @param ipolicy: the dict to fill
1708
    @rtype: dict
1709
    @return: a copy of passed ipolicy with missing keys filled from
1710
      the cluster defaults
1711

1712
    """
1713
    return FillIPolicy(self.ipolicy, ipolicy)
1714

    
1715

    
1716
class BlockDevStatus(ConfigObject):
1717
  """Config object representing the status of a block device."""
1718
  __slots__ = [
1719
    "dev_path",
1720
    "major",
1721
    "minor",
1722
    "sync_percent",
1723
    "estimated_time",
1724
    "is_degraded",
1725
    "ldisk_status",
1726
    ]
1727

    
1728

    
1729
class ImportExportStatus(ConfigObject):
1730
  """Config object representing the status of an import or export."""
1731
  __slots__ = [
1732
    "recent_output",
1733
    "listen_port",
1734
    "connected",
1735
    "progress_mbytes",
1736
    "progress_throughput",
1737
    "progress_eta",
1738
    "progress_percent",
1739
    "exit_status",
1740
    "error_message",
1741
    ] + _TIMESTAMPS
1742

    
1743

    
1744
class ImportExportOptions(ConfigObject):
1745
  """Options for import/export daemon
1746

1747
  @ivar key_name: X509 key name (None for cluster certificate)
1748
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1749
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1750
  @ivar magic: Used to ensure the connection goes to the right disk
1751
  @ivar ipv6: Whether to use IPv6
1752
  @ivar connect_timeout: Number of seconds for establishing connection
1753

1754
  """
1755
  __slots__ = [
1756
    "key_name",
1757
    "ca_pem",
1758
    "compress",
1759
    "magic",
1760
    "ipv6",
1761
    "connect_timeout",
1762
    ]
1763

    
1764

    
1765
class ConfdRequest(ConfigObject):
1766
  """Object holding a confd request.
1767

1768
  @ivar protocol: confd protocol version
1769
  @ivar type: confd query type
1770
  @ivar query: query request
1771
  @ivar rsalt: requested reply salt
1772

1773
  """
1774
  __slots__ = [
1775
    "protocol",
1776
    "type",
1777
    "query",
1778
    "rsalt",
1779
    ]
1780

    
1781

    
1782
class ConfdReply(ConfigObject):
1783
  """Object holding a confd reply.
1784

1785
  @ivar protocol: confd protocol version
1786
  @ivar status: reply status code (ok, error)
1787
  @ivar answer: confd query reply
1788
  @ivar serial: configuration serial number
1789

1790
  """
1791
  __slots__ = [
1792
    "protocol",
1793
    "status",
1794
    "answer",
1795
    "serial",
1796
    ]
1797

    
1798

    
1799
class QueryFieldDefinition(ConfigObject):
1800
  """Object holding a query field definition.
1801

1802
  @ivar name: Field name
1803
  @ivar title: Human-readable title
1804
  @ivar kind: Field type
1805
  @ivar doc: Human-readable description
1806

1807
  """
1808
  __slots__ = [
1809
    "name",
1810
    "title",
1811
    "kind",
1812
    "doc",
1813
    ]
1814

    
1815

    
1816
class _QueryResponseBase(ConfigObject):
1817
  __slots__ = [
1818
    "fields",
1819
    ]
1820

    
1821
  def ToDict(self):
1822
    """Custom function for serializing.
1823

1824
    """
1825
    mydict = super(_QueryResponseBase, self).ToDict()
1826
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1827
    return mydict
1828

    
1829
  @classmethod
1830
  def FromDict(cls, val):
1831
    """Custom function for de-serializing.
1832

1833
    """
1834
    obj = super(_QueryResponseBase, cls).FromDict(val)
1835
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1836
    return obj
1837

    
1838

    
1839
class QueryRequest(ConfigObject):
1840
  """Object holding a query request.
1841

1842
  """
1843
  __slots__ = [
1844
    "what",
1845
    "fields",
1846
    "qfilter",
1847
    ]
1848

    
1849

    
1850
class QueryResponse(_QueryResponseBase):
1851
  """Object holding the response to a query.
1852

1853
  @ivar fields: List of L{QueryFieldDefinition} objects
1854
  @ivar data: Requested data
1855

1856
  """
1857
  __slots__ = [
1858
    "data",
1859
    ]
1860

    
1861

    
1862
class QueryFieldsRequest(ConfigObject):
1863
  """Object holding a request for querying available fields.
1864

1865
  """
1866
  __slots__ = [
1867
    "what",
1868
    "fields",
1869
    ]
1870

    
1871

    
1872
class QueryFieldsResponse(_QueryResponseBase):
1873
  """Object holding the response to a query for fields.
1874

1875
  @ivar fields: List of L{QueryFieldDefinition} objects
1876

1877
  """
1878
  __slots__ = [
1879
    ]
1880

    
1881

    
1882
class MigrationStatus(ConfigObject):
1883
  """Object holding the status of a migration.
1884

1885
  """
1886
  __slots__ = [
1887
    "status",
1888
    "transferred_ram",
1889
    "total_ram",
1890
    ]
1891

    
1892

    
1893
class InstanceConsole(ConfigObject):
1894
  """Object describing how to access the console of an instance.
1895

1896
  """
1897
  __slots__ = [
1898
    "instance",
1899
    "kind",
1900
    "message",
1901
    "host",
1902
    "port",
1903
    "user",
1904
    "command",
1905
    "display",
1906
    ]
1907

    
1908
  def Validate(self):
1909
    """Validates contents of this object.
1910

1911
    """
1912
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1913
    assert self.instance, "Missing instance name"
1914
    assert self.message or self.kind in [constants.CONS_SSH,
1915
                                         constants.CONS_SPICE,
1916
                                         constants.CONS_VNC]
1917
    assert self.host or self.kind == constants.CONS_MESSAGE
1918
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1919
                                      constants.CONS_SSH]
1920
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1921
                                      constants.CONS_SPICE,
1922
                                      constants.CONS_VNC]
1923
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1924
                                         constants.CONS_SPICE,
1925
                                         constants.CONS_VNC]
1926
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1927
                                         constants.CONS_SPICE,
1928
                                         constants.CONS_SSH]
1929
    return True
1930

    
1931

    
1932
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1933
  """Simple wrapper over ConfigParse that allows serialization.
1934

1935
  This class is basically ConfigParser.SafeConfigParser with two
1936
  additional methods that allow it to serialize/unserialize to/from a
1937
  buffer.
1938

1939
  """
1940
  def Dumps(self):
1941
    """Dump this instance and return the string representation."""
1942
    buf = StringIO()
1943
    self.write(buf)
1944
    return buf.getvalue()
1945

    
1946
  @classmethod
1947
  def Loads(cls, data):
1948
    """Load data from a string."""
1949
    buf = StringIO(data)
1950
    cfp = cls()
1951
    cfp.readfp(buf)
1952
    return cfp