Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 8a147bba

History | View | Annotate | Download (56.9 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
  # other items which we know we can directly copy (immutables)
109
  for key in constants.IPOLICY_PARAMETERS:
110
    ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
111

    
112
  return ret_dict
113

    
114

    
115
def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
116
  """Fills the disk parameter defaults.
117

118
  @see FillDict: For parameters and return value
119

120
  """
121
  assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
122

    
123
  return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
124
                             skip_keys=skip_keys))
125
              for dt in constants.DISK_TEMPLATES)
126

    
127

    
128
def UpgradeGroupedParams(target, defaults):
129
  """Update all groups for the target parameter.
130

131
  @type target: dict of dicts
132
  @param target: {group: {parameter: value}}
133
  @type defaults: dict
134
  @param defaults: default parameter values
135

136
  """
137
  if target is None:
138
    target = {constants.PP_DEFAULT: defaults}
139
  else:
140
    for group in target:
141
      target[group] = FillDict(defaults, target[group])
142
  return target
143

    
144

    
145
def UpgradeBeParams(target):
146
  """Update the be parameters dict to the new format.
147

148
  @type target: dict
149
  @param target: "be" parameters dict
150

151
  """
152
  if constants.BE_MEMORY in target:
153
    memory = target[constants.BE_MEMORY]
154
    target[constants.BE_MAXMEM] = memory
155
    target[constants.BE_MINMEM] = memory
156
    del target[constants.BE_MEMORY]
157

    
158

    
159
def UpgradeDiskParams(diskparams):
160
  """Upgrade the disk parameters.
161

162
  @type diskparams: dict
163
  @param diskparams: disk parameters to upgrade
164
  @rtype: dict
165
  @return: the upgraded disk parameters dict
166

167
  """
168
  if diskparams is None:
169
    result = constants.DISK_DT_DEFAULTS.copy()
170
  else:
171
    result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
172

    
173
  return result
174

    
175

    
176
def UpgradeNDParams(ndparams):
177
  """Upgrade ndparams structure.
178

179
  @type ndparams: dict
180
  @param ndparams: disk parameters to upgrade
181
  @rtype: dict
182
  @return: the upgraded node parameters dict
183

184
  """
185
  if ndparams is None:
186
    ndparams = {}
187

    
188
  return FillDict(constants.NDC_DEFAULTS, ndparams)
189

    
190

    
191
def MakeEmptyIPolicy():
192
  """Create empty IPolicy dictionary.
193

194
  """
195
  return dict([
196
    (constants.ISPECS_MIN, {}),
197
    (constants.ISPECS_MAX, {}),
198
    (constants.ISPECS_STD, {}),
199
    ])
200

    
201

    
202
def CreateIPolicyFromOpts(ispecs_mem_size=None,
203
                          ispecs_cpu_count=None,
204
                          ispecs_disk_count=None,
205
                          ispecs_disk_size=None,
206
                          ispecs_nic_count=None,
207
                          ipolicy_disk_templates=None,
208
                          ipolicy_vcpu_ratio=None,
209
                          group_ipolicy=False,
210
                          allowed_values=None,
211
                          fill_all=False):
212
  """Creation of instance policy based on command line options.
213

214
  @param fill_all: whether for cluster policies we should ensure that
215
    all values are filled
216

217

218
  """
219
  # prepare ipolicy dict
220
  ipolicy_transposed = {
221
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
222
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
223
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
224
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
225
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
226
    }
227

    
228
  # first, check that the values given are correct
229
  if group_ipolicy:
230
    forced_type = TISPECS_GROUP_TYPES
231
  else:
232
    forced_type = TISPECS_CLUSTER_TYPES
233

    
234
  for specs in ipolicy_transposed.values():
235
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
236

    
237
  # then transpose
238
  ipolicy_out = MakeEmptyIPolicy()
239
  for name, specs in ipolicy_transposed.iteritems():
240
    assert name in constants.ISPECS_PARAMETERS
241
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
242
      ipolicy_out[key][name] = val
243

    
244
  # no filldict for non-dicts
245
  if not group_ipolicy and fill_all:
246
    if ipolicy_disk_templates is None:
247
      ipolicy_disk_templates = constants.DISK_TEMPLATES
248
    if ipolicy_vcpu_ratio is None:
249
      ipolicy_vcpu_ratio = \
250
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
251
  if ipolicy_disk_templates is not None:
252
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
253
  if ipolicy_vcpu_ratio is not None:
254
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
255

    
256
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
257

    
258
  return ipolicy_out
259

    
260

    
261
class ConfigObject(object):
262
  """A generic config object.
263

264
  It has the following properties:
265

266
    - provides somewhat safe recursive unpickling and pickling for its classes
267
    - unset attributes which are defined in slots are always returned
268
      as None instead of raising an error
269

270
  Classes derived from this must always declare __slots__ (we use many
271
  config objects and the memory reduction is useful)
272

273
  """
274
  __slots__ = []
275

    
276
  def __init__(self, **kwargs):
277
    for k, v in kwargs.iteritems():
278
      setattr(self, k, v)
279

    
280
  def __getattr__(self, name):
281
    if name not in self._all_slots():
282
      raise AttributeError("Invalid object attribute %s.%s" %
283
                           (type(self).__name__, name))
284
    return None
285

    
286
  def __setstate__(self, state):
287
    slots = self._all_slots()
288
    for name in state:
289
      if name in slots:
290
        setattr(self, name, state[name])
291

    
292
  @classmethod
293
  def _all_slots(cls):
294
    """Compute the list of all declared slots for a class.
295

296
    """
297
    slots = []
298
    for parent in cls.__mro__:
299
      slots.extend(getattr(parent, "__slots__", []))
300
    return slots
301

    
302
  #: Public getter for the defined slots
303
  GetAllSlots = _all_slots
304

    
305
  def ToDict(self):
306
    """Convert to a dict holding only standard python types.
307

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

314
    """
315
    result = {}
316
    for name in self._all_slots():
317
      value = getattr(self, name, None)
318
      if value is not None:
319
        result[name] = value
320
    return result
321

    
322
  __getstate__ = ToDict
323

    
324
  @classmethod
325
  def FromDict(cls, val):
326
    """Create an object from a dictionary.
327

328
    This generic routine takes a dict, instantiates a new instance of
329
    the given class, and sets attributes based on the dict content.
330

331
    As for `ToDict`, this does not work if the class has children
332
    who are ConfigObjects themselves (e.g. the nics list in an
333
    Instance), in which case the object should subclass the function
334
    and alter the objects.
335

336
    """
337
    if not isinstance(val, dict):
338
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
339
                                      " expected dict, got %s" % type(val))
340
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
341
    obj = cls(**val_str) # pylint: disable=W0142
342
    return obj
343

    
344
  @staticmethod
345
  def _ContainerToDicts(container):
346
    """Convert the elements of a container to standard python types.
347

348
    This method converts a container with elements derived from
349
    ConfigData to standard python types. If the container is a dict,
350
    we don't touch the keys, only the values.
351

352
    """
353
    if isinstance(container, dict):
354
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
355
    elif isinstance(container, (list, tuple, set, frozenset)):
356
      ret = [elem.ToDict() for elem in container]
357
    else:
358
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
359
                      type(container))
360
    return ret
361

    
362
  @staticmethod
363
  def _ContainerFromDicts(source, c_type, e_type):
364
    """Convert a container from standard python types.
365

366
    This method converts a container with standard python types to
367
    ConfigData objects. If the container is a dict, we don't touch the
368
    keys, only the values.
369

370
    """
371
    if not isinstance(c_type, type):
372
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
373
                      " not a type" % type(c_type))
374
    if source is None:
375
      source = c_type()
376
    if c_type is dict:
377
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
378
    elif c_type in (list, tuple, set, frozenset):
379
      ret = c_type([e_type.FromDict(elem) for elem in source])
380
    else:
381
      raise TypeError("Invalid container type %s passed to"
382
                      " _ContainerFromDicts" % c_type)
383
    return ret
384

    
385
  def Copy(self):
386
    """Makes a deep copy of the current object and its children.
387

388
    """
389
    dict_form = self.ToDict()
390
    clone_obj = self.__class__.FromDict(dict_form)
391
    return clone_obj
392

    
393
  def __repr__(self):
394
    """Implement __repr__ for ConfigObjects."""
395
    return repr(self.ToDict())
396

    
397
  def UpgradeConfig(self):
398
    """Fill defaults for missing configuration values.
399

400
    This method will be called at configuration load time, and its
401
    implementation will be object dependent.
402

403
    """
404
    pass
405

    
406

    
407
class TaggableObject(ConfigObject):
408
  """An generic class supporting tags.
409

410
  """
411
  __slots__ = ["tags"]
412
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
413

    
414
  @classmethod
415
  def ValidateTag(cls, tag):
416
    """Check if a tag is valid.
417

418
    If the tag is invalid, an errors.TagError will be raised. The
419
    function has no return value.
420

421
    """
422
    if not isinstance(tag, basestring):
423
      raise errors.TagError("Invalid tag type (not a string)")
424
    if len(tag) > constants.MAX_TAG_LEN:
425
      raise errors.TagError("Tag too long (>%d characters)" %
426
                            constants.MAX_TAG_LEN)
427
    if not tag:
428
      raise errors.TagError("Tags cannot be empty")
429
    if not cls.VALID_TAG_RE.match(tag):
430
      raise errors.TagError("Tag contains invalid characters")
431

    
432
  def GetTags(self):
433
    """Return the tags list.
434

435
    """
436
    tags = getattr(self, "tags", None)
437
    if tags is None:
438
      tags = self.tags = set()
439
    return tags
440

    
441
  def AddTag(self, tag):
442
    """Add a new tag.
443

444
    """
445
    self.ValidateTag(tag)
446
    tags = self.GetTags()
447
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
448
      raise errors.TagError("Too many tags")
449
    self.GetTags().add(tag)
450

    
451
  def RemoveTag(self, tag):
452
    """Remove a tag.
453

454
    """
455
    self.ValidateTag(tag)
456
    tags = self.GetTags()
457
    try:
458
      tags.remove(tag)
459
    except KeyError:
460
      raise errors.TagError("Tag not found")
461

    
462
  def ToDict(self):
463
    """Taggable-object-specific conversion to standard python types.
464

465
    This replaces the tags set with a list.
466

467
    """
468
    bo = super(TaggableObject, self).ToDict()
469

    
470
    tags = bo.get("tags", None)
471
    if isinstance(tags, set):
472
      bo["tags"] = list(tags)
473
    return bo
474

    
475
  @classmethod
476
  def FromDict(cls, val):
477
    """Custom function for instances.
478

479
    """
480
    obj = super(TaggableObject, cls).FromDict(val)
481
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
482
      obj.tags = set(obj.tags)
483
    return obj
484

    
485

    
486
class MasterNetworkParameters(ConfigObject):
487
  """Network configuration parameters for the master
488

489
  @ivar name: master name
490
  @ivar ip: master IP
491
  @ivar netmask: master netmask
492
  @ivar netdev: master network device
493
  @ivar ip_family: master IP family
494

495
  """
496
  __slots__ = [
497
    "name",
498
    "ip",
499
    "netmask",
500
    "netdev",
501
    "ip_family"
502
    ]
503

    
504

    
505
class ConfigData(ConfigObject):
506
  """Top-level config object."""
507
  __slots__ = [
508
    "version",
509
    "cluster",
510
    "nodes",
511
    "nodegroups",
512
    "instances",
513
    "serial_no",
514
    ] + _TIMESTAMPS
515

    
516
  def ToDict(self):
517
    """Custom function for top-level config data.
518

519
    This just replaces the list of instances, nodes and the cluster
520
    with standard python types.
521

522
    """
523
    mydict = super(ConfigData, self).ToDict()
524
    mydict["cluster"] = mydict["cluster"].ToDict()
525
    for key in "nodes", "instances", "nodegroups":
526
      mydict[key] = self._ContainerToDicts(mydict[key])
527

    
528
    return mydict
529

    
530
  @classmethod
531
  def FromDict(cls, val):
532
    """Custom function for top-level config data
533

534
    """
535
    obj = super(ConfigData, cls).FromDict(val)
536
    obj.cluster = Cluster.FromDict(obj.cluster)
537
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
538
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
539
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
540
    return obj
541

    
542
  def HasAnyDiskOfType(self, dev_type):
543
    """Check if in there is at disk of the given type in the configuration.
544

545
    @type dev_type: L{constants.LDS_BLOCK}
546
    @param dev_type: the type to look for
547
    @rtype: boolean
548
    @return: boolean indicating if a disk of the given type was found or not
549

550
    """
551
    for instance in self.instances.values():
552
      for disk in instance.disks:
553
        if disk.IsBasedOnDiskType(dev_type):
554
          return True
555
    return False
556

    
557
  def UpgradeConfig(self):
558
    """Fill defaults for missing configuration values.
559

560
    """
561
    self.cluster.UpgradeConfig()
562
    for node in self.nodes.values():
563
      node.UpgradeConfig()
564
    for instance in self.instances.values():
565
      instance.UpgradeConfig()
566
    if self.nodegroups is None:
567
      self.nodegroups = {}
568
    for nodegroup in self.nodegroups.values():
569
      nodegroup.UpgradeConfig()
570
    if self.cluster.drbd_usermode_helper is None:
571
      # To decide if we set an helper let's check if at least one instance has
572
      # a DRBD disk. This does not cover all the possible scenarios but it
573
      # gives a good approximation.
574
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
575
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
576

    
577

    
578
class NIC(ConfigObject):
579
  """Config object representing a network card."""
580
  __slots__ = ["mac", "ip", "nicparams"]
581

    
582
  @classmethod
583
  def CheckParameterSyntax(cls, nicparams):
584
    """Check the given parameters for validity.
585

586
    @type nicparams:  dict
587
    @param nicparams: dictionary with parameter names/value
588
    @raise errors.ConfigurationError: when a parameter is not valid
589

590
    """
591
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
592
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
593
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
594
      raise errors.ConfigurationError(err)
595

    
596
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
597
        not nicparams[constants.NIC_LINK]):
598
      err = "Missing bridged nic link"
599
      raise errors.ConfigurationError(err)
600

    
601

    
602
class Disk(ConfigObject):
603
  """Config object representing a block device."""
604
  __slots__ = ["dev_type", "logical_id", "physical_id",
605
               "children", "iv_name", "size", "mode", "params"]
606

    
607
  def CreateOnSecondary(self):
608
    """Test if this device needs to be created on a secondary node."""
609
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
610

    
611
  def AssembleOnSecondary(self):
612
    """Test if this device needs to be assembled on a secondary node."""
613
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
614

    
615
  def OpenOnSecondary(self):
616
    """Test if this device needs to be opened on a secondary node."""
617
    return self.dev_type in (constants.LD_LV,)
618

    
619
  def StaticDevPath(self):
620
    """Return the device path if this device type has a static one.
621

622
    Some devices (LVM for example) live always at the same /dev/ path,
623
    irrespective of their status. For such devices, we return this
624
    path, for others we return None.
625

626
    @warning: The path returned is not a normalized pathname; callers
627
        should check that it is a valid path.
628

629
    """
630
    if self.dev_type == constants.LD_LV:
631
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
632
    elif self.dev_type == constants.LD_BLOCKDEV:
633
      return self.logical_id[1]
634
    elif self.dev_type == constants.LD_RBD:
635
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
636
    return None
637

    
638
  def ChildrenNeeded(self):
639
    """Compute the needed number of children for activation.
640

641
    This method will return either -1 (all children) or a positive
642
    number denoting the minimum number of children needed for
643
    activation (only mirrored devices will usually return >=0).
644

645
    Currently, only DRBD8 supports diskless activation (therefore we
646
    return 0), for all other we keep the previous semantics and return
647
    -1.
648

649
    """
650
    if self.dev_type == constants.LD_DRBD8:
651
      return 0
652
    return -1
653

    
654
  def IsBasedOnDiskType(self, dev_type):
655
    """Check if the disk or its children are based on the given type.
656

657
    @type dev_type: L{constants.LDS_BLOCK}
658
    @param dev_type: the type to look for
659
    @rtype: boolean
660
    @return: boolean indicating if a device of the given type was found or not
661

662
    """
663
    if self.children:
664
      for child in self.children:
665
        if child.IsBasedOnDiskType(dev_type):
666
          return True
667
    return self.dev_type == dev_type
668

    
669
  def GetNodes(self, node):
670
    """This function returns the nodes this device lives on.
671

672
    Given the node on which the parent of the device lives on (or, in
673
    case of a top-level device, the primary node of the devices'
674
    instance), this function will return a list of nodes on which this
675
    devices needs to (or can) be assembled.
676

677
    """
678
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
679
                         constants.LD_BLOCKDEV, constants.LD_RBD]:
680
      result = [node]
681
    elif self.dev_type in constants.LDS_DRBD:
682
      result = [self.logical_id[0], self.logical_id[1]]
683
      if node not in result:
684
        raise errors.ConfigurationError("DRBD device passed unknown node")
685
    else:
686
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
687
    return result
688

    
689
  def ComputeNodeTree(self, parent_node):
690
    """Compute the node/disk tree for this disk and its children.
691

692
    This method, given the node on which the parent disk lives, will
693
    return the list of all (node, disk) pairs which describe the disk
694
    tree in the most compact way. For example, a drbd/lvm stack
695
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
696
    which represents all the top-level devices on the nodes.
697

698
    """
699
    my_nodes = self.GetNodes(parent_node)
700
    result = [(node, self) for node in my_nodes]
701
    if not self.children:
702
      # leaf device
703
      return result
704
    for node in my_nodes:
705
      for child in self.children:
706
        child_result = child.ComputeNodeTree(node)
707
        if len(child_result) == 1:
708
          # child (and all its descendants) is simple, doesn't split
709
          # over multiple hosts, so we don't need to describe it, our
710
          # own entry for this node describes it completely
711
          continue
712
        else:
713
          # check if child nodes differ from my nodes; note that
714
          # subdisk can differ from the child itself, and be instead
715
          # one of its descendants
716
          for subnode, subdisk in child_result:
717
            if subnode not in my_nodes:
718
              result.append((subnode, subdisk))
719
            # otherwise child is under our own node, so we ignore this
720
            # entry (but probably the other results in the list will
721
            # be different)
722
    return result
723

    
724
  def ComputeGrowth(self, amount):
725
    """Compute the per-VG growth requirements.
726

727
    This only works for VG-based disks.
728

729
    @type amount: integer
730
    @param amount: the desired increase in (user-visible) disk space
731
    @rtype: dict
732
    @return: a dictionary of volume-groups and the required size
733

734
    """
735
    if self.dev_type == constants.LD_LV:
736
      return {self.logical_id[0]: amount}
737
    elif self.dev_type == constants.LD_DRBD8:
738
      if self.children:
739
        return self.children[0].ComputeGrowth(amount)
740
      else:
741
        return {}
742
    else:
743
      # Other disk types do not require VG space
744
      return {}
745

    
746
  def RecordGrow(self, amount):
747
    """Update the size of this disk after growth.
748

749
    This method recurses over the disks's children and updates their
750
    size correspondigly. The method needs to be kept in sync with the
751
    actual algorithms from bdev.
752

753
    """
754
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
755
                         constants.LD_RBD):
756
      self.size += amount
757
    elif self.dev_type == constants.LD_DRBD8:
758
      if self.children:
759
        self.children[0].RecordGrow(amount)
760
      self.size += amount
761
    else:
762
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
763
                                   " disk type %s" % self.dev_type)
764

    
765
  def Update(self, size=None, mode=None):
766
    """Apply changes to size and mode.
767

768
    """
769
    if self.dev_type == constants.LD_DRBD8:
770
      if self.children:
771
        self.children[0].Update(size=size, mode=mode)
772
    else:
773
      assert not self.children
774

    
775
    if size is not None:
776
      self.size = size
777
    if mode is not None:
778
      self.mode = mode
779

    
780
  def UnsetSize(self):
781
    """Sets recursively the size to zero for the disk and its children.
782

783
    """
784
    if self.children:
785
      for child in self.children:
786
        child.UnsetSize()
787
    self.size = 0
788

    
789
  def SetPhysicalID(self, target_node, nodes_ip):
790
    """Convert the logical ID to the physical ID.
791

792
    This is used only for drbd, which needs ip/port configuration.
793

794
    The routine descends down and updates its children also, because
795
    this helps when the only the top device is passed to the remote
796
    node.
797

798
    Arguments:
799
      - target_node: the node we wish to configure for
800
      - nodes_ip: a mapping of node name to ip
801

802
    The target_node must exist in in nodes_ip, and must be one of the
803
    nodes in the logical ID for each of the DRBD devices encountered
804
    in the disk tree.
805

806
    """
807
    if self.children:
808
      for child in self.children:
809
        child.SetPhysicalID(target_node, nodes_ip)
810

    
811
    if self.logical_id is None and self.physical_id is not None:
812
      return
813
    if self.dev_type in constants.LDS_DRBD:
814
      pnode, snode, port, pminor, sminor, secret = self.logical_id
815
      if target_node not in (pnode, snode):
816
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
817
                                        target_node)
818
      pnode_ip = nodes_ip.get(pnode, None)
819
      snode_ip = nodes_ip.get(snode, None)
820
      if pnode_ip is None or snode_ip is None:
821
        raise errors.ConfigurationError("Can't find primary or secondary node"
822
                                        " for %s" % str(self))
823
      p_data = (pnode_ip, port)
824
      s_data = (snode_ip, port)
825
      if pnode == target_node:
826
        self.physical_id = p_data + s_data + (pminor, secret)
827
      else: # it must be secondary, we tested above
828
        self.physical_id = s_data + p_data + (sminor, secret)
829
    else:
830
      self.physical_id = self.logical_id
831
    return
832

    
833
  def ToDict(self):
834
    """Disk-specific conversion to standard python types.
835

836
    This replaces the children lists of objects with lists of
837
    standard python types.
838

839
    """
840
    bo = super(Disk, self).ToDict()
841

    
842
    for attr in ("children",):
843
      alist = bo.get(attr, None)
844
      if alist:
845
        bo[attr] = self._ContainerToDicts(alist)
846
    return bo
847

    
848
  @classmethod
849
  def FromDict(cls, val):
850
    """Custom function for Disks
851

852
    """
853
    obj = super(Disk, cls).FromDict(val)
854
    if obj.children:
855
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
856
    if obj.logical_id and isinstance(obj.logical_id, list):
857
      obj.logical_id = tuple(obj.logical_id)
858
    if obj.physical_id and isinstance(obj.physical_id, list):
859
      obj.physical_id = tuple(obj.physical_id)
860
    if obj.dev_type in constants.LDS_DRBD:
861
      # we need a tuple of length six here
862
      if len(obj.logical_id) < 6:
863
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
864
    return obj
865

    
866
  def __str__(self):
867
    """Custom str() formatter for disks.
868

869
    """
870
    if self.dev_type == constants.LD_LV:
871
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
872
    elif self.dev_type in constants.LDS_DRBD:
873
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
874
      val = "<DRBD8("
875
      if self.physical_id is None:
876
        phy = "unconfigured"
877
      else:
878
        phy = ("configured as %s:%s %s:%s" %
879
               (self.physical_id[0], self.physical_id[1],
880
                self.physical_id[2], self.physical_id[3]))
881

    
882
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
883
              (node_a, minor_a, node_b, minor_b, port, phy))
884
      if self.children and self.children.count(None) == 0:
885
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
886
      else:
887
        val += "no local storage"
888
    else:
889
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
890
             (self.dev_type, self.logical_id, self.physical_id, self.children))
891
    if self.iv_name is None:
892
      val += ", not visible"
893
    else:
894
      val += ", visible as /dev/%s" % self.iv_name
895
    if isinstance(self.size, int):
896
      val += ", size=%dm)>" % self.size
897
    else:
898
      val += ", size='%s')>" % (self.size,)
899
    return val
900

    
901
  def Verify(self):
902
    """Checks that this disk is correctly configured.
903

904
    """
905
    all_errors = []
906
    if self.mode not in constants.DISK_ACCESS_SET:
907
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
908
    return all_errors
909

    
910
  def UpgradeConfig(self):
911
    """Fill defaults for missing configuration values.
912

913
    """
914
    if self.children:
915
      for child in self.children:
916
        child.UpgradeConfig()
917

    
918
    if not self.params:
919
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
920
    else:
921
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
922
                             self.params)
923
    # add here config upgrade for this disk
924

    
925

    
926
class InstancePolicy(ConfigObject):
927
  """Config object representing instance policy limits dictionary.
928

929

930
  Note that this object is not actually used in the config, it's just
931
  used as a placeholder for a few functions.
932

933
  """
934
  @classmethod
935
  def CheckParameterSyntax(cls, ipolicy):
936
    """ Check the instance policy for validity.
937

938
    """
939
    for param in constants.ISPECS_PARAMETERS:
940
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
941
    if constants.IPOLICY_DTS in ipolicy:
942
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
943
    for key in constants.IPOLICY_PARAMETERS:
944
      if key in ipolicy:
945
        InstancePolicy.CheckParameter(key, ipolicy[key])
946
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
947
    if wrong_keys:
948
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
949
                                      utils.CommaJoin(wrong_keys))
950

    
951
  @classmethod
952
  def CheckISpecSyntax(cls, ipolicy, name):
953
    """Check the instance policy for validity on a given key.
954

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

958
    @type ipolicy: dict
959
    @param ipolicy: dictionary with min, max, std specs
960
    @type name: string
961
    @param name: what are the limits for
962
    @raise errors.ConfigureError: when specs for given name are not valid
963

964
    """
965
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
966
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
967
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
968
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
969
           (name,
970
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
971
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
972
            ipolicy[constants.ISPECS_STD].get(name, "-")))
973
    if min_v > std_v or std_v > max_v:
974
      raise errors.ConfigurationError(err)
975

    
976
  @classmethod
977
  def CheckDiskTemplates(cls, disk_templates):
978
    """Checks the disk templates for validity.
979

980
    """
981
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
982
    if wrong:
983
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
984
                                      utils.CommaJoin(wrong))
985

    
986
  @classmethod
987
  def CheckParameter(cls, key, value):
988
    """Checks a parameter.
989

990
    Currently we expect all parameters to be float values.
991

992
    """
993
    try:
994
      float(value)
995
    except (TypeError, ValueError), err:
996
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
997
                                      " '%s', error: %s" % (key, value, err))
998

    
999

    
1000
class Instance(TaggableObject):
1001
  """Config object representing an instance."""
1002
  __slots__ = [
1003
    "name",
1004
    "primary_node",
1005
    "os",
1006
    "hypervisor",
1007
    "hvparams",
1008
    "beparams",
1009
    "osparams",
1010
    "admin_state",
1011
    "nics",
1012
    "disks",
1013
    "disk_template",
1014
    "network_port",
1015
    "serial_no",
1016
    ] + _TIMESTAMPS + _UUID
1017

    
1018
  def _ComputeSecondaryNodes(self):
1019
    """Compute the list of secondary nodes.
1020

1021
    This is a simple wrapper over _ComputeAllNodes.
1022

1023
    """
1024
    all_nodes = set(self._ComputeAllNodes())
1025
    all_nodes.discard(self.primary_node)
1026
    return tuple(all_nodes)
1027

    
1028
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1029
                             "List of secondary nodes")
1030

    
1031
  def _ComputeAllNodes(self):
1032
    """Compute the list of all nodes.
1033

1034
    Since the data is already there (in the drbd disks), keeping it as
1035
    a separate normal attribute is redundant and if not properly
1036
    synchronised can cause problems. Thus it's better to compute it
1037
    dynamically.
1038

1039
    """
1040
    def _Helper(nodes, device):
1041
      """Recursively computes nodes given a top device."""
1042
      if device.dev_type in constants.LDS_DRBD:
1043
        nodea, nodeb = device.logical_id[:2]
1044
        nodes.add(nodea)
1045
        nodes.add(nodeb)
1046
      if device.children:
1047
        for child in device.children:
1048
          _Helper(nodes, child)
1049

    
1050
    all_nodes = set()
1051
    all_nodes.add(self.primary_node)
1052
    for device in self.disks:
1053
      _Helper(all_nodes, device)
1054
    return tuple(all_nodes)
1055

    
1056
  all_nodes = property(_ComputeAllNodes, None, None,
1057
                       "List of all nodes of the instance")
1058

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

1062
    This function figures out what logical volumes should belong on
1063
    which nodes, recursing through a device tree.
1064

1065
    @param lvmap: optional dictionary to receive the
1066
        'node' : ['lv', ...] data.
1067

1068
    @return: None if lvmap arg is given, otherwise, a dictionary of
1069
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1070
        volumeN is of the form "vg_name/lv_name", compatible with
1071
        GetVolumeList()
1072

1073
    """
1074
    if node == None:
1075
      node = self.primary_node
1076

    
1077
    if lvmap is None:
1078
      lvmap = {
1079
        node: [],
1080
        }
1081
      ret = lvmap
1082
    else:
1083
      if not node in lvmap:
1084
        lvmap[node] = []
1085
      ret = None
1086

    
1087
    if not devs:
1088
      devs = self.disks
1089

    
1090
    for dev in devs:
1091
      if dev.dev_type == constants.LD_LV:
1092
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1093

    
1094
      elif dev.dev_type in constants.LDS_DRBD:
1095
        if dev.children:
1096
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1097
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1098

    
1099
      elif dev.children:
1100
        self.MapLVsByNode(lvmap, dev.children, node)
1101

    
1102
    return ret
1103

    
1104
  def FindDisk(self, idx):
1105
    """Find a disk given having a specified index.
1106

1107
    This is just a wrapper that does validation of the index.
1108

1109
    @type idx: int
1110
    @param idx: the disk index
1111
    @rtype: L{Disk}
1112
    @return: the corresponding disk
1113
    @raise errors.OpPrereqError: when the given index is not valid
1114

1115
    """
1116
    try:
1117
      idx = int(idx)
1118
      return self.disks[idx]
1119
    except (TypeError, ValueError), err:
1120
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1121
                                 errors.ECODE_INVAL)
1122
    except IndexError:
1123
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1124
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1125
                                 errors.ECODE_INVAL)
1126

    
1127
  def ToDict(self):
1128
    """Instance-specific conversion to standard python types.
1129

1130
    This replaces the children lists of objects with lists of standard
1131
    python types.
1132

1133
    """
1134
    bo = super(Instance, self).ToDict()
1135

    
1136
    for attr in "nics", "disks":
1137
      alist = bo.get(attr, None)
1138
      if alist:
1139
        nlist = self._ContainerToDicts(alist)
1140
      else:
1141
        nlist = []
1142
      bo[attr] = nlist
1143
    return bo
1144

    
1145
  @classmethod
1146
  def FromDict(cls, val):
1147
    """Custom function for instances.
1148

1149
    """
1150
    if "admin_state" not in val:
1151
      if val.get("admin_up", False):
1152
        val["admin_state"] = constants.ADMINST_UP
1153
      else:
1154
        val["admin_state"] = constants.ADMINST_DOWN
1155
    if "admin_up" in val:
1156
      del val["admin_up"]
1157
    obj = super(Instance, cls).FromDict(val)
1158
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1159
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1160
    return obj
1161

    
1162
  def UpgradeConfig(self):
1163
    """Fill defaults for missing configuration values.
1164

1165
    """
1166
    for nic in self.nics:
1167
      nic.UpgradeConfig()
1168
    for disk in self.disks:
1169
      disk.UpgradeConfig()
1170
    if self.hvparams:
1171
      for key in constants.HVC_GLOBALS:
1172
        try:
1173
          del self.hvparams[key]
1174
        except KeyError:
1175
          pass
1176
    if self.osparams is None:
1177
      self.osparams = {}
1178
    UpgradeBeParams(self.beparams)
1179

    
1180

    
1181
class OS(ConfigObject):
1182
  """Config object representing an operating system.
1183

1184
  @type supported_parameters: list
1185
  @ivar supported_parameters: a list of tuples, name and description,
1186
      containing the supported parameters by this OS
1187

1188
  @type VARIANT_DELIM: string
1189
  @cvar VARIANT_DELIM: the variant delimiter
1190

1191
  """
1192
  __slots__ = [
1193
    "name",
1194
    "path",
1195
    "api_versions",
1196
    "create_script",
1197
    "export_script",
1198
    "import_script",
1199
    "rename_script",
1200
    "verify_script",
1201
    "supported_variants",
1202
    "supported_parameters",
1203
    ]
1204

    
1205
  VARIANT_DELIM = "+"
1206

    
1207
  @classmethod
1208
  def SplitNameVariant(cls, name):
1209
    """Splits the name into the proper name and variant.
1210

1211
    @param name: the OS (unprocessed) name
1212
    @rtype: list
1213
    @return: a list of two elements; if the original name didn't
1214
        contain a variant, it's returned as an empty string
1215

1216
    """
1217
    nv = name.split(cls.VARIANT_DELIM, 1)
1218
    if len(nv) == 1:
1219
      nv.append("")
1220
    return nv
1221

    
1222
  @classmethod
1223
  def GetName(cls, name):
1224
    """Returns the proper name of the os (without the variant).
1225

1226
    @param name: the OS (unprocessed) name
1227

1228
    """
1229
    return cls.SplitNameVariant(name)[0]
1230

    
1231
  @classmethod
1232
  def GetVariant(cls, name):
1233
    """Returns the variant the os (without the base name).
1234

1235
    @param name: the OS (unprocessed) name
1236

1237
    """
1238
    return cls.SplitNameVariant(name)[1]
1239

    
1240

    
1241
class NodeHvState(ConfigObject):
1242
  """Hypvervisor state on a node.
1243

1244
  @ivar mem_total: Total amount of memory
1245
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1246
    available)
1247
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1248
    rounding
1249
  @ivar mem_inst: Memory used by instances living on node
1250
  @ivar cpu_total: Total node CPU core count
1251
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1252

1253
  """
1254
  __slots__ = [
1255
    "mem_total",
1256
    "mem_node",
1257
    "mem_hv",
1258
    "mem_inst",
1259
    "cpu_total",
1260
    "cpu_node",
1261
    ] + _TIMESTAMPS
1262

    
1263

    
1264
class NodeDiskState(ConfigObject):
1265
  """Disk state on a node.
1266

1267
  """
1268
  __slots__ = [
1269
    "total",
1270
    "reserved",
1271
    "overhead",
1272
    ] + _TIMESTAMPS
1273

    
1274

    
1275
class Node(TaggableObject):
1276
  """Config object representing a node.
1277

1278
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1279
  @ivar hv_state_static: Hypervisor state overriden by user
1280
  @ivar disk_state: Disk state (e.g. free space)
1281
  @ivar disk_state_static: Disk state overriden by user
1282

1283
  """
1284
  __slots__ = [
1285
    "name",
1286
    "primary_ip",
1287
    "secondary_ip",
1288
    "serial_no",
1289
    "master_candidate",
1290
    "offline",
1291
    "drained",
1292
    "group",
1293
    "master_capable",
1294
    "vm_capable",
1295
    "ndparams",
1296
    "powered",
1297
    "hv_state",
1298
    "hv_state_static",
1299
    "disk_state",
1300
    "disk_state_static",
1301
    ] + _TIMESTAMPS + _UUID
1302

    
1303
  def UpgradeConfig(self):
1304
    """Fill defaults for missing configuration values.
1305

1306
    """
1307
    # pylint: disable=E0203
1308
    # because these are "defined" via slots, not manually
1309
    if self.master_capable is None:
1310
      self.master_capable = True
1311

    
1312
    if self.vm_capable is None:
1313
      self.vm_capable = True
1314

    
1315
    if self.ndparams is None:
1316
      self.ndparams = {}
1317

    
1318
    if self.powered is None:
1319
      self.powered = True
1320

    
1321
  def ToDict(self):
1322
    """Custom function for serializing.
1323

1324
    """
1325
    data = super(Node, self).ToDict()
1326

    
1327
    hv_state = data.get("hv_state", None)
1328
    if hv_state is not None:
1329
      data["hv_state"] = self._ContainerToDicts(hv_state)
1330

    
1331
    disk_state = data.get("disk_state", None)
1332
    if disk_state is not None:
1333
      data["disk_state"] = \
1334
        dict((key, self._ContainerToDicts(value))
1335
             for (key, value) in disk_state.items())
1336

    
1337
    return data
1338

    
1339
  @classmethod
1340
  def FromDict(cls, val):
1341
    """Custom function for deserializing.
1342

1343
    """
1344
    obj = super(Node, cls).FromDict(val)
1345

    
1346
    if obj.hv_state is not None:
1347
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1348

    
1349
    if obj.disk_state is not None:
1350
      obj.disk_state = \
1351
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1352
             for (key, value) in obj.disk_state.items())
1353

    
1354
    return obj
1355

    
1356

    
1357
class NodeGroup(TaggableObject):
1358
  """Config object representing a node group."""
1359
  __slots__ = [
1360
    "name",
1361
    "members",
1362
    "ndparams",
1363
    "diskparams",
1364
    "ipolicy",
1365
    "serial_no",
1366
    "hv_state_static",
1367
    "disk_state_static",
1368
    "alloc_policy",
1369
    ] + _TIMESTAMPS + _UUID
1370

    
1371
  def ToDict(self):
1372
    """Custom function for nodegroup.
1373

1374
    This discards the members object, which gets recalculated and is only kept
1375
    in memory.
1376

1377
    """
1378
    mydict = super(NodeGroup, self).ToDict()
1379
    del mydict["members"]
1380
    return mydict
1381

    
1382
  @classmethod
1383
  def FromDict(cls, val):
1384
    """Custom function for nodegroup.
1385

1386
    The members slot is initialized to an empty list, upon deserialization.
1387

1388
    """
1389
    obj = super(NodeGroup, cls).FromDict(val)
1390
    obj.members = []
1391
    return obj
1392

    
1393
  def UpgradeConfig(self):
1394
    """Fill defaults for missing configuration values.
1395

1396
    """
1397
    if self.ndparams is None:
1398
      self.ndparams = {}
1399

    
1400
    if self.serial_no is None:
1401
      self.serial_no = 1
1402

    
1403
    if self.alloc_policy is None:
1404
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1405

    
1406
    # We only update mtime, and not ctime, since we would not be able
1407
    # to provide a correct value for creation time.
1408
    if self.mtime is None:
1409
      self.mtime = time.time()
1410

    
1411
    self.diskparams = UpgradeDiskParams(self.diskparams)
1412
    if self.ipolicy is None:
1413
      self.ipolicy = MakeEmptyIPolicy()
1414

    
1415
  def FillND(self, node):
1416
    """Return filled out ndparams for L{objects.Node}
1417

1418
    @type node: L{objects.Node}
1419
    @param node: A Node object to fill
1420
    @return a copy of the node's ndparams with defaults filled
1421

1422
    """
1423
    return self.SimpleFillND(node.ndparams)
1424

    
1425
  def SimpleFillND(self, ndparams):
1426
    """Fill a given ndparams dict with defaults.
1427

1428
    @type ndparams: dict
1429
    @param ndparams: the dict to fill
1430
    @rtype: dict
1431
    @return: a copy of the passed in ndparams with missing keys filled
1432
        from the node group defaults
1433

1434
    """
1435
    return FillDict(self.ndparams, ndparams)
1436

    
1437

    
1438
class Cluster(TaggableObject):
1439
  """Config object representing the cluster."""
1440
  __slots__ = [
1441
    "serial_no",
1442
    "rsahostkeypub",
1443
    "highest_used_port",
1444
    "tcpudp_port_pool",
1445
    "mac_prefix",
1446
    "volume_group_name",
1447
    "reserved_lvs",
1448
    "drbd_usermode_helper",
1449
    "default_bridge",
1450
    "default_hypervisor",
1451
    "master_node",
1452
    "master_ip",
1453
    "master_netdev",
1454
    "master_netmask",
1455
    "use_external_mip_script",
1456
    "cluster_name",
1457
    "file_storage_dir",
1458
    "shared_file_storage_dir",
1459
    "enabled_hypervisors",
1460
    "hvparams",
1461
    "ipolicy",
1462
    "os_hvp",
1463
    "beparams",
1464
    "osparams",
1465
    "nicparams",
1466
    "ndparams",
1467
    "diskparams",
1468
    "candidate_pool_size",
1469
    "modify_etc_hosts",
1470
    "modify_ssh_setup",
1471
    "maintain_node_health",
1472
    "uid_pool",
1473
    "default_iallocator",
1474
    "hidden_os",
1475
    "blacklisted_os",
1476
    "primary_ip_family",
1477
    "prealloc_wipe_disks",
1478
    "hv_state_static",
1479
    "disk_state_static",
1480
    ] + _TIMESTAMPS + _UUID
1481

    
1482
  def UpgradeConfig(self):
1483
    """Fill defaults for missing configuration values.
1484

1485
    """
1486
    # pylint: disable=E0203
1487
    # because these are "defined" via slots, not manually
1488
    if self.hvparams is None:
1489
      self.hvparams = constants.HVC_DEFAULTS
1490
    else:
1491
      for hypervisor in self.hvparams:
1492
        self.hvparams[hypervisor] = FillDict(
1493
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1494

    
1495
    if self.os_hvp is None:
1496
      self.os_hvp = {}
1497

    
1498
    # osparams added before 2.2
1499
    if self.osparams is None:
1500
      self.osparams = {}
1501

    
1502
    self.ndparams = UpgradeNDParams(self.ndparams)
1503

    
1504
    self.beparams = UpgradeGroupedParams(self.beparams,
1505
                                         constants.BEC_DEFAULTS)
1506
    for beparams_group in self.beparams:
1507
      UpgradeBeParams(self.beparams[beparams_group])
1508

    
1509
    migrate_default_bridge = not self.nicparams
1510
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1511
                                          constants.NICC_DEFAULTS)
1512
    if migrate_default_bridge:
1513
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1514
        self.default_bridge
1515

    
1516
    if self.modify_etc_hosts is None:
1517
      self.modify_etc_hosts = True
1518

    
1519
    if self.modify_ssh_setup is None:
1520
      self.modify_ssh_setup = True
1521

    
1522
    # default_bridge is no longer used in 2.1. The slot is left there to
1523
    # support auto-upgrading. It can be removed once we decide to deprecate
1524
    # upgrading straight from 2.0.
1525
    if self.default_bridge is not None:
1526
      self.default_bridge = None
1527

    
1528
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1529
    # code can be removed once upgrading straight from 2.0 is deprecated.
1530
    if self.default_hypervisor is not None:
1531
      self.enabled_hypervisors = ([self.default_hypervisor] +
1532
        [hvname for hvname in self.enabled_hypervisors
1533
         if hvname != self.default_hypervisor])
1534
      self.default_hypervisor = None
1535

    
1536
    # maintain_node_health added after 2.1.1
1537
    if self.maintain_node_health is None:
1538
      self.maintain_node_health = False
1539

    
1540
    if self.uid_pool is None:
1541
      self.uid_pool = []
1542

    
1543
    if self.default_iallocator is None:
1544
      self.default_iallocator = ""
1545

    
1546
    # reserved_lvs added before 2.2
1547
    if self.reserved_lvs is None:
1548
      self.reserved_lvs = []
1549

    
1550
    # hidden and blacklisted operating systems added before 2.2.1
1551
    if self.hidden_os is None:
1552
      self.hidden_os = []
1553

    
1554
    if self.blacklisted_os is None:
1555
      self.blacklisted_os = []
1556

    
1557
    # primary_ip_family added before 2.3
1558
    if self.primary_ip_family is None:
1559
      self.primary_ip_family = AF_INET
1560

    
1561
    if self.master_netmask is None:
1562
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1563
      self.master_netmask = ipcls.iplen
1564

    
1565
    if self.prealloc_wipe_disks is None:
1566
      self.prealloc_wipe_disks = False
1567

    
1568
    # shared_file_storage_dir added before 2.5
1569
    if self.shared_file_storage_dir is None:
1570
      self.shared_file_storage_dir = ""
1571

    
1572
    if self.use_external_mip_script is None:
1573
      self.use_external_mip_script = False
1574

    
1575
    self.diskparams = UpgradeDiskParams(self.diskparams)
1576

    
1577
    # instance policy added before 2.6
1578
    if self.ipolicy is None:
1579
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1580
    else:
1581
      # we can either make sure to upgrade the ipolicy always, or only
1582
      # do it in some corner cases (e.g. missing keys); note that this
1583
      # will break any removal of keys from the ipolicy dict
1584
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1585

    
1586
  @property
1587
  def primary_hypervisor(self):
1588
    """The first hypervisor is the primary.
1589

1590
    Useful, for example, for L{Node}'s hv/disk state.
1591

1592
    """
1593
    return self.enabled_hypervisors[0]
1594

    
1595
  def ToDict(self):
1596
    """Custom function for cluster.
1597

1598
    """
1599
    mydict = super(Cluster, self).ToDict()
1600
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1601
    return mydict
1602

    
1603
  @classmethod
1604
  def FromDict(cls, val):
1605
    """Custom function for cluster.
1606

1607
    """
1608
    obj = super(Cluster, cls).FromDict(val)
1609
    if not isinstance(obj.tcpudp_port_pool, set):
1610
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1611
    return obj
1612

    
1613
  def SimpleFillDP(self, diskparams):
1614
    """Fill a given diskparams dict with cluster defaults.
1615

1616
    @param diskparams: The diskparams
1617
    @return: The defaults dict
1618

1619
    """
1620
    return FillDiskParams(self.diskparams, diskparams)
1621

    
1622
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1623
    """Get the default hypervisor parameters for the cluster.
1624

1625
    @param hypervisor: the hypervisor name
1626
    @param os_name: if specified, we'll also update the defaults for this OS
1627
    @param skip_keys: if passed, list of keys not to use
1628
    @return: the defaults dict
1629

1630
    """
1631
    if skip_keys is None:
1632
      skip_keys = []
1633

    
1634
    fill_stack = [self.hvparams.get(hypervisor, {})]
1635
    if os_name is not None:
1636
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1637
      fill_stack.append(os_hvp)
1638

    
1639
    ret_dict = {}
1640
    for o_dict in fill_stack:
1641
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1642

    
1643
    return ret_dict
1644

    
1645
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1646
    """Fill a given hvparams dict with cluster defaults.
1647

1648
    @type hv_name: string
1649
    @param hv_name: the hypervisor to use
1650
    @type os_name: string
1651
    @param os_name: the OS to use for overriding the hypervisor defaults
1652
    @type skip_globals: boolean
1653
    @param skip_globals: if True, the global hypervisor parameters will
1654
        not be filled
1655
    @rtype: dict
1656
    @return: a copy of the given hvparams with missing keys filled from
1657
        the cluster defaults
1658

1659
    """
1660
    if skip_globals:
1661
      skip_keys = constants.HVC_GLOBALS
1662
    else:
1663
      skip_keys = []
1664

    
1665
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1666
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1667

    
1668
  def FillHV(self, instance, skip_globals=False):
1669
    """Fill an instance's hvparams dict with cluster defaults.
1670

1671
    @type instance: L{objects.Instance}
1672
    @param instance: the instance parameter to fill
1673
    @type skip_globals: boolean
1674
    @param skip_globals: if True, the global hypervisor parameters will
1675
        not be filled
1676
    @rtype: dict
1677
    @return: a copy of the instance's hvparams with missing keys filled from
1678
        the cluster defaults
1679

1680
    """
1681
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1682
                             instance.hvparams, skip_globals)
1683

    
1684
  def SimpleFillBE(self, beparams):
1685
    """Fill a given beparams dict with cluster defaults.
1686

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

1693
    """
1694
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1695

    
1696
  def FillBE(self, instance):
1697
    """Fill an instance's beparams dict with cluster defaults.
1698

1699
    @type instance: L{objects.Instance}
1700
    @param instance: the instance parameter to fill
1701
    @rtype: dict
1702
    @return: a copy of the instance's beparams with missing keys filled from
1703
        the cluster defaults
1704

1705
    """
1706
    return self.SimpleFillBE(instance.beparams)
1707

    
1708
  def SimpleFillNIC(self, nicparams):
1709
    """Fill a given nicparams dict with cluster defaults.
1710

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

1717
    """
1718
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1719

    
1720
  def SimpleFillOS(self, os_name, os_params):
1721
    """Fill an instance's osparams dict with cluster defaults.
1722

1723
    @type os_name: string
1724
    @param os_name: the OS name to use
1725
    @type os_params: dict
1726
    @param os_params: the dict to fill with default values
1727
    @rtype: dict
1728
    @return: a copy of the instance's osparams with missing keys filled from
1729
        the cluster defaults
1730

1731
    """
1732
    name_only = os_name.split("+", 1)[0]
1733
    # base OS
1734
    result = self.osparams.get(name_only, {})
1735
    # OS with variant
1736
    result = FillDict(result, self.osparams.get(os_name, {}))
1737
    # specified params
1738
    return FillDict(result, os_params)
1739

    
1740
  @staticmethod
1741
  def SimpleFillHvState(hv_state):
1742
    """Fill an hv_state sub dict with cluster defaults.
1743

1744
    """
1745
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1746

    
1747
  @staticmethod
1748
  def SimpleFillDiskState(disk_state):
1749
    """Fill an disk_state sub dict with cluster defaults.
1750

1751
    """
1752
    return FillDict(constants.DS_DEFAULTS, disk_state)
1753

    
1754
  def FillND(self, node, nodegroup):
1755
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1756

1757
    @type node: L{objects.Node}
1758
    @param node: A Node object to fill
1759
    @type nodegroup: L{objects.NodeGroup}
1760
    @param nodegroup: A Node object to fill
1761
    @return a copy of the node's ndparams with defaults filled
1762

1763
    """
1764
    return self.SimpleFillND(nodegroup.FillND(node))
1765

    
1766
  def SimpleFillND(self, ndparams):
1767
    """Fill a given ndparams dict with defaults.
1768

1769
    @type ndparams: dict
1770
    @param ndparams: the dict to fill
1771
    @rtype: dict
1772
    @return: a copy of the passed in ndparams with missing keys filled
1773
        from the cluster defaults
1774

1775
    """
1776
    return FillDict(self.ndparams, ndparams)
1777

    
1778
  def SimpleFillIPolicy(self, ipolicy):
1779
    """ Fill instance policy dict with defaults.
1780

1781
    @type ipolicy: dict
1782
    @param ipolicy: the dict to fill
1783
    @rtype: dict
1784
    @return: a copy of passed ipolicy with missing keys filled from
1785
      the cluster defaults
1786

1787
    """
1788
    return FillIPolicy(self.ipolicy, ipolicy)
1789

    
1790

    
1791
class BlockDevStatus(ConfigObject):
1792
  """Config object representing the status of a block device."""
1793
  __slots__ = [
1794
    "dev_path",
1795
    "major",
1796
    "minor",
1797
    "sync_percent",
1798
    "estimated_time",
1799
    "is_degraded",
1800
    "ldisk_status",
1801
    ]
1802

    
1803

    
1804
class ImportExportStatus(ConfigObject):
1805
  """Config object representing the status of an import or export."""
1806
  __slots__ = [
1807
    "recent_output",
1808
    "listen_port",
1809
    "connected",
1810
    "progress_mbytes",
1811
    "progress_throughput",
1812
    "progress_eta",
1813
    "progress_percent",
1814
    "exit_status",
1815
    "error_message",
1816
    ] + _TIMESTAMPS
1817

    
1818

    
1819
class ImportExportOptions(ConfigObject):
1820
  """Options for import/export daemon
1821

1822
  @ivar key_name: X509 key name (None for cluster certificate)
1823
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1824
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1825
  @ivar magic: Used to ensure the connection goes to the right disk
1826
  @ivar ipv6: Whether to use IPv6
1827
  @ivar connect_timeout: Number of seconds for establishing connection
1828

1829
  """
1830
  __slots__ = [
1831
    "key_name",
1832
    "ca_pem",
1833
    "compress",
1834
    "magic",
1835
    "ipv6",
1836
    "connect_timeout",
1837
    ]
1838

    
1839

    
1840
class ConfdRequest(ConfigObject):
1841
  """Object holding a confd request.
1842

1843
  @ivar protocol: confd protocol version
1844
  @ivar type: confd query type
1845
  @ivar query: query request
1846
  @ivar rsalt: requested reply salt
1847

1848
  """
1849
  __slots__ = [
1850
    "protocol",
1851
    "type",
1852
    "query",
1853
    "rsalt",
1854
    ]
1855

    
1856

    
1857
class ConfdReply(ConfigObject):
1858
  """Object holding a confd reply.
1859

1860
  @ivar protocol: confd protocol version
1861
  @ivar status: reply status code (ok, error)
1862
  @ivar answer: confd query reply
1863
  @ivar serial: configuration serial number
1864

1865
  """
1866
  __slots__ = [
1867
    "protocol",
1868
    "status",
1869
    "answer",
1870
    "serial",
1871
    ]
1872

    
1873

    
1874
class QueryFieldDefinition(ConfigObject):
1875
  """Object holding a query field definition.
1876

1877
  @ivar name: Field name
1878
  @ivar title: Human-readable title
1879
  @ivar kind: Field type
1880
  @ivar doc: Human-readable description
1881

1882
  """
1883
  __slots__ = [
1884
    "name",
1885
    "title",
1886
    "kind",
1887
    "doc",
1888
    ]
1889

    
1890

    
1891
class _QueryResponseBase(ConfigObject):
1892
  __slots__ = [
1893
    "fields",
1894
    ]
1895

    
1896
  def ToDict(self):
1897
    """Custom function for serializing.
1898

1899
    """
1900
    mydict = super(_QueryResponseBase, self).ToDict()
1901
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1902
    return mydict
1903

    
1904
  @classmethod
1905
  def FromDict(cls, val):
1906
    """Custom function for de-serializing.
1907

1908
    """
1909
    obj = super(_QueryResponseBase, cls).FromDict(val)
1910
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1911
    return obj
1912

    
1913

    
1914
class QueryResponse(_QueryResponseBase):
1915
  """Object holding the response to a query.
1916

1917
  @ivar fields: List of L{QueryFieldDefinition} objects
1918
  @ivar data: Requested data
1919

1920
  """
1921
  __slots__ = [
1922
    "data",
1923
    ]
1924

    
1925

    
1926
class QueryFieldsRequest(ConfigObject):
1927
  """Object holding a request for querying available fields.
1928

1929
  """
1930
  __slots__ = [
1931
    "what",
1932
    "fields",
1933
    ]
1934

    
1935

    
1936
class QueryFieldsResponse(_QueryResponseBase):
1937
  """Object holding the response to a query for fields.
1938

1939
  @ivar fields: List of L{QueryFieldDefinition} objects
1940

1941
  """
1942
  __slots__ = [
1943
    ]
1944

    
1945

    
1946
class MigrationStatus(ConfigObject):
1947
  """Object holding the status of a migration.
1948

1949
  """
1950
  __slots__ = [
1951
    "status",
1952
    "transferred_ram",
1953
    "total_ram",
1954
    ]
1955

    
1956

    
1957
class InstanceConsole(ConfigObject):
1958
  """Object describing how to access the console of an instance.
1959

1960
  """
1961
  __slots__ = [
1962
    "instance",
1963
    "kind",
1964
    "message",
1965
    "host",
1966
    "port",
1967
    "user",
1968
    "command",
1969
    "display",
1970
    ]
1971

    
1972
  def Validate(self):
1973
    """Validates contents of this object.
1974

1975
    """
1976
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1977
    assert self.instance, "Missing instance name"
1978
    assert self.message or self.kind in [constants.CONS_SSH,
1979
                                         constants.CONS_SPICE,
1980
                                         constants.CONS_VNC]
1981
    assert self.host or self.kind == constants.CONS_MESSAGE
1982
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1983
                                      constants.CONS_SSH]
1984
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1985
                                      constants.CONS_SPICE,
1986
                                      constants.CONS_VNC]
1987
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1988
                                         constants.CONS_SPICE,
1989
                                         constants.CONS_VNC]
1990
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1991
                                         constants.CONS_SPICE,
1992
                                         constants.CONS_SSH]
1993
    return True
1994

    
1995

    
1996
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1997
  """Simple wrapper over ConfigParse that allows serialization.
1998

1999
  This class is basically ConfigParser.SafeConfigParser with two
2000
  additional methods that allow it to serialize/unserialize to/from a
2001
  buffer.
2002

2003
  """
2004
  def Dumps(self):
2005
    """Dump this instance and return the string representation."""
2006
    buf = StringIO()
2007
    self.write(buf)
2008
    return buf.getvalue()
2009

    
2010
  @classmethod
2011
  def Loads(cls, data):
2012
    """Load data from a string."""
2013
    buf = StringIO(data)
2014
    cfp = cls()
2015
    cfp.readfp(buf)
2016
    return cfp