Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 415feb2e

History | View | Annotate | Download (56.7 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 UpgradeGroupedParams(target, defaults):
116
  """Update all groups for the target parameter.
117

118
  @type target: dict of dicts
119
  @param target: {group: {parameter: value}}
120
  @type defaults: dict
121
  @param defaults: default parameter values
122

123
  """
124
  if target is None:
125
    target = {constants.PP_DEFAULT: defaults}
126
  else:
127
    for group in target:
128
      target[group] = FillDict(defaults, target[group])
129
  return target
130

    
131

    
132
def UpgradeBeParams(target):
133
  """Update the be parameters dict to the new format.
134

135
  @type target: dict
136
  @param target: "be" parameters dict
137

138
  """
139
  if constants.BE_MEMORY in target:
140
    memory = target[constants.BE_MEMORY]
141
    target[constants.BE_MAXMEM] = memory
142
    target[constants.BE_MINMEM] = memory
143
    del target[constants.BE_MEMORY]
144

    
145

    
146
def UpgradeDiskParams(diskparams):
147
  """Upgrade the disk parameters.
148

149
  @type diskparams: dict
150
  @param diskparams: disk parameters to upgrade
151
  @rtype: dict
152
  @return: the upgraded disk parameters dict
153

154
  """
155
  if diskparams is None:
156
    result = constants.DISK_DT_DEFAULTS.copy()
157
  else:
158
    # Update the disk parameter values for each disk template.
159
    # The code iterates over constants.DISK_TEMPLATES because new templates
160
    # might have been added.
161
    result = dict((dt, FillDict(constants.DISK_DT_DEFAULTS[dt],
162
                                diskparams.get(dt, {})))
163
                  for dt in constants.DISK_TEMPLATES)
164

    
165
  return result
166

    
167

    
168
def UpgradeNDParams(ndparams):
169
  """Upgrade ndparams structure.
170

171
  @type ndparams: dict
172
  @param ndparams: disk parameters to upgrade
173
  @rtype: dict
174
  @return: the upgraded node parameters dict
175

176
  """
177
  if ndparams is None:
178
    ndparams = {}
179

    
180
  return FillDict(constants.NDC_DEFAULTS, ndparams)
181

    
182

    
183
def MakeEmptyIPolicy():
184
  """Create empty IPolicy dictionary.
185

186
  """
187
  return dict([
188
    (constants.ISPECS_MIN, {}),
189
    (constants.ISPECS_MAX, {}),
190
    (constants.ISPECS_STD, {}),
191
    ])
192

    
193

    
194
def CreateIPolicyFromOpts(ispecs_mem_size=None,
195
                          ispecs_cpu_count=None,
196
                          ispecs_disk_count=None,
197
                          ispecs_disk_size=None,
198
                          ispecs_nic_count=None,
199
                          ipolicy_disk_templates=None,
200
                          ipolicy_vcpu_ratio=None,
201
                          group_ipolicy=False,
202
                          allowed_values=None,
203
                          fill_all=False):
204
  """Creation of instance policy based on command line options.
205

206
  @param fill_all: whether for cluster policies we should ensure that
207
    all values are filled
208

209

210
  """
211
  # prepare ipolicy dict
212
  ipolicy_transposed = {
213
    constants.ISPEC_MEM_SIZE: ispecs_mem_size,
214
    constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
215
    constants.ISPEC_DISK_COUNT: ispecs_disk_count,
216
    constants.ISPEC_DISK_SIZE: ispecs_disk_size,
217
    constants.ISPEC_NIC_COUNT: ispecs_nic_count,
218
    }
219

    
220
  # first, check that the values given are correct
221
  if group_ipolicy:
222
    forced_type = TISPECS_GROUP_TYPES
223
  else:
224
    forced_type = TISPECS_CLUSTER_TYPES
225

    
226
  for specs in ipolicy_transposed.values():
227
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
228

    
229
  # then transpose
230
  ipolicy_out = MakeEmptyIPolicy()
231
  for name, specs in ipolicy_transposed.iteritems():
232
    assert name in constants.ISPECS_PARAMETERS
233
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
234
      ipolicy_out[key][name] = val
235

    
236
  # no filldict for non-dicts
237
  if not group_ipolicy and fill_all:
238
    if ipolicy_disk_templates is None:
239
      ipolicy_disk_templates = constants.DISK_TEMPLATES
240
    if ipolicy_vcpu_ratio is None:
241
      ipolicy_vcpu_ratio = \
242
        constants.IPOLICY_DEFAULTS[constants.IPOLICY_VCPU_RATIO]
243
  if ipolicy_disk_templates is not None:
244
    ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
245
  if ipolicy_vcpu_ratio is not None:
246
    ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
247

    
248
  assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
249

    
250
  return ipolicy_out
251

    
252

    
253
class ConfigObject(object):
254
  """A generic config object.
255

256
  It has the following properties:
257

258
    - provides somewhat safe recursive unpickling and pickling for its classes
259
    - unset attributes which are defined in slots are always returned
260
      as None instead of raising an error
261

262
  Classes derived from this must always declare __slots__ (we use many
263
  config objects and the memory reduction is useful)
264

265
  """
266
  __slots__ = []
267

    
268
  def __init__(self, **kwargs):
269
    for k, v in kwargs.iteritems():
270
      setattr(self, k, v)
271

    
272
  def __getattr__(self, name):
273
    if name not in self._all_slots():
274
      raise AttributeError("Invalid object attribute %s.%s" %
275
                           (type(self).__name__, name))
276
    return None
277

    
278
  def __setstate__(self, state):
279
    slots = self._all_slots()
280
    for name in state:
281
      if name in slots:
282
        setattr(self, name, state[name])
283

    
284
  @classmethod
285
  def _all_slots(cls):
286
    """Compute the list of all declared slots for a class.
287

288
    """
289
    slots = []
290
    for parent in cls.__mro__:
291
      slots.extend(getattr(parent, "__slots__", []))
292
    return slots
293

    
294
  #: Public getter for the defined slots
295
  GetAllSlots = _all_slots
296

    
297
  def ToDict(self):
298
    """Convert to a dict holding only standard python types.
299

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

306
    """
307
    result = {}
308
    for name in self._all_slots():
309
      value = getattr(self, name, None)
310
      if value is not None:
311
        result[name] = value
312
    return result
313

    
314
  __getstate__ = ToDict
315

    
316
  @classmethod
317
  def FromDict(cls, val):
318
    """Create an object from a dictionary.
319

320
    This generic routine takes a dict, instantiates a new instance of
321
    the given class, and sets attributes based on the dict content.
322

323
    As for `ToDict`, this does not work if the class has children
324
    who are ConfigObjects themselves (e.g. the nics list in an
325
    Instance), in which case the object should subclass the function
326
    and alter the objects.
327

328
    """
329
    if not isinstance(val, dict):
330
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
331
                                      " expected dict, got %s" % type(val))
332
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
333
    obj = cls(**val_str) # pylint: disable=W0142
334
    return obj
335

    
336
  @staticmethod
337
  def _ContainerToDicts(container):
338
    """Convert the elements of a container to standard python types.
339

340
    This method converts a container with elements derived from
341
    ConfigData to standard python types. If the container is a dict,
342
    we don't touch the keys, only the values.
343

344
    """
345
    if isinstance(container, dict):
346
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
347
    elif isinstance(container, (list, tuple, set, frozenset)):
348
      ret = [elem.ToDict() for elem in container]
349
    else:
350
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
351
                      type(container))
352
    return ret
353

    
354
  @staticmethod
355
  def _ContainerFromDicts(source, c_type, e_type):
356
    """Convert a container from standard python types.
357

358
    This method converts a container with standard python types to
359
    ConfigData objects. If the container is a dict, we don't touch the
360
    keys, only the values.
361

362
    """
363
    if not isinstance(c_type, type):
364
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
365
                      " not a type" % type(c_type))
366
    if source is None:
367
      source = c_type()
368
    if c_type is dict:
369
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
370
    elif c_type in (list, tuple, set, frozenset):
371
      ret = c_type([e_type.FromDict(elem) for elem in source])
372
    else:
373
      raise TypeError("Invalid container type %s passed to"
374
                      " _ContainerFromDicts" % c_type)
375
    return ret
376

    
377
  def Copy(self):
378
    """Makes a deep copy of the current object and its children.
379

380
    """
381
    dict_form = self.ToDict()
382
    clone_obj = self.__class__.FromDict(dict_form)
383
    return clone_obj
384

    
385
  def __repr__(self):
386
    """Implement __repr__ for ConfigObjects."""
387
    return repr(self.ToDict())
388

    
389
  def UpgradeConfig(self):
390
    """Fill defaults for missing configuration values.
391

392
    This method will be called at configuration load time, and its
393
    implementation will be object dependent.
394

395
    """
396
    pass
397

    
398

    
399
class TaggableObject(ConfigObject):
400
  """An generic class supporting tags.
401

402
  """
403
  __slots__ = ["tags"]
404
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
405

    
406
  @classmethod
407
  def ValidateTag(cls, tag):
408
    """Check if a tag is valid.
409

410
    If the tag is invalid, an errors.TagError will be raised. The
411
    function has no return value.
412

413
    """
414
    if not isinstance(tag, basestring):
415
      raise errors.TagError("Invalid tag type (not a string)")
416
    if len(tag) > constants.MAX_TAG_LEN:
417
      raise errors.TagError("Tag too long (>%d characters)" %
418
                            constants.MAX_TAG_LEN)
419
    if not tag:
420
      raise errors.TagError("Tags cannot be empty")
421
    if not cls.VALID_TAG_RE.match(tag):
422
      raise errors.TagError("Tag contains invalid characters")
423

    
424
  def GetTags(self):
425
    """Return the tags list.
426

427
    """
428
    tags = getattr(self, "tags", None)
429
    if tags is None:
430
      tags = self.tags = set()
431
    return tags
432

    
433
  def AddTag(self, tag):
434
    """Add a new tag.
435

436
    """
437
    self.ValidateTag(tag)
438
    tags = self.GetTags()
439
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
440
      raise errors.TagError("Too many tags")
441
    self.GetTags().add(tag)
442

    
443
  def RemoveTag(self, tag):
444
    """Remove a tag.
445

446
    """
447
    self.ValidateTag(tag)
448
    tags = self.GetTags()
449
    try:
450
      tags.remove(tag)
451
    except KeyError:
452
      raise errors.TagError("Tag not found")
453

    
454
  def ToDict(self):
455
    """Taggable-object-specific conversion to standard python types.
456

457
    This replaces the tags set with a list.
458

459
    """
460
    bo = super(TaggableObject, self).ToDict()
461

    
462
    tags = bo.get("tags", None)
463
    if isinstance(tags, set):
464
      bo["tags"] = list(tags)
465
    return bo
466

    
467
  @classmethod
468
  def FromDict(cls, val):
469
    """Custom function for instances.
470

471
    """
472
    obj = super(TaggableObject, cls).FromDict(val)
473
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
474
      obj.tags = set(obj.tags)
475
    return obj
476

    
477

    
478
class MasterNetworkParameters(ConfigObject):
479
  """Network configuration parameters for the master
480

481
  @ivar name: master name
482
  @ivar ip: master IP
483
  @ivar netmask: master netmask
484
  @ivar netdev: master network device
485
  @ivar ip_family: master IP family
486

487
  """
488
  __slots__ = [
489
    "name",
490
    "ip",
491
    "netmask",
492
    "netdev",
493
    "ip_family"
494
    ]
495

    
496

    
497
class ConfigData(ConfigObject):
498
  """Top-level config object."""
499
  __slots__ = [
500
    "version",
501
    "cluster",
502
    "nodes",
503
    "nodegroups",
504
    "instances",
505
    "serial_no",
506
    ] + _TIMESTAMPS
507

    
508
  def ToDict(self):
509
    """Custom function for top-level config data.
510

511
    This just replaces the list of instances, nodes and the cluster
512
    with standard python types.
513

514
    """
515
    mydict = super(ConfigData, self).ToDict()
516
    mydict["cluster"] = mydict["cluster"].ToDict()
517
    for key in "nodes", "instances", "nodegroups":
518
      mydict[key] = self._ContainerToDicts(mydict[key])
519

    
520
    return mydict
521

    
522
  @classmethod
523
  def FromDict(cls, val):
524
    """Custom function for top-level config data
525

526
    """
527
    obj = super(ConfigData, cls).FromDict(val)
528
    obj.cluster = Cluster.FromDict(obj.cluster)
529
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
530
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
531
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
532
    return obj
533

    
534
  def HasAnyDiskOfType(self, dev_type):
535
    """Check if in there is at disk of the given type in the configuration.
536

537
    @type dev_type: L{constants.LDS_BLOCK}
538
    @param dev_type: the type to look for
539
    @rtype: boolean
540
    @return: boolean indicating if a disk of the given type was found or not
541

542
    """
543
    for instance in self.instances.values():
544
      for disk in instance.disks:
545
        if disk.IsBasedOnDiskType(dev_type):
546
          return True
547
    return False
548

    
549
  def UpgradeConfig(self):
550
    """Fill defaults for missing configuration values.
551

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

    
569

    
570
class NIC(ConfigObject):
571
  """Config object representing a network card."""
572
  __slots__ = ["mac", "ip", "nicparams"]
573

    
574
  @classmethod
575
  def CheckParameterSyntax(cls, nicparams):
576
    """Check the given parameters for validity.
577

578
    @type nicparams:  dict
579
    @param nicparams: dictionary with parameter names/value
580
    @raise errors.ConfigurationError: when a parameter is not valid
581

582
    """
583
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
584
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
585
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
586
      raise errors.ConfigurationError(err)
587

    
588
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
589
        not nicparams[constants.NIC_LINK]):
590
      err = "Missing bridged nic link"
591
      raise errors.ConfigurationError(err)
592

    
593

    
594
class Disk(ConfigObject):
595
  """Config object representing a block device."""
596
  __slots__ = ["dev_type", "logical_id", "physical_id",
597
               "children", "iv_name", "size", "mode", "params"]
598

    
599
  def CreateOnSecondary(self):
600
    """Test if this device needs to be created on a secondary node."""
601
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
602

    
603
  def AssembleOnSecondary(self):
604
    """Test if this device needs to be assembled on a secondary node."""
605
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
606

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

    
611
  def StaticDevPath(self):
612
    """Return the device path if this device type has a static one.
613

614
    Some devices (LVM for example) live always at the same /dev/ path,
615
    irrespective of their status. For such devices, we return this
616
    path, for others we return None.
617

618
    @warning: The path returned is not a normalized pathname; callers
619
        should check that it is a valid path.
620

621
    """
622
    if self.dev_type == constants.LD_LV:
623
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
624
    elif self.dev_type == constants.LD_BLOCKDEV:
625
      return self.logical_id[1]
626
    elif self.dev_type == constants.LD_RBD:
627
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
628
    return None
629

    
630
  def ChildrenNeeded(self):
631
    """Compute the needed number of children for activation.
632

633
    This method will return either -1 (all children) or a positive
634
    number denoting the minimum number of children needed for
635
    activation (only mirrored devices will usually return >=0).
636

637
    Currently, only DRBD8 supports diskless activation (therefore we
638
    return 0), for all other we keep the previous semantics and return
639
    -1.
640

641
    """
642
    if self.dev_type == constants.LD_DRBD8:
643
      return 0
644
    return -1
645

    
646
  def IsBasedOnDiskType(self, dev_type):
647
    """Check if the disk or its children are based on the given type.
648

649
    @type dev_type: L{constants.LDS_BLOCK}
650
    @param dev_type: the type to look for
651
    @rtype: boolean
652
    @return: boolean indicating if a device of the given type was found or not
653

654
    """
655
    if self.children:
656
      for child in self.children:
657
        if child.IsBasedOnDiskType(dev_type):
658
          return True
659
    return self.dev_type == dev_type
660

    
661
  def GetNodes(self, node):
662
    """This function returns the nodes this device lives on.
663

664
    Given the node on which the parent of the device lives on (or, in
665
    case of a top-level device, the primary node of the devices'
666
    instance), this function will return a list of nodes on which this
667
    devices needs to (or can) be assembled.
668

669
    """
670
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
671
                         constants.LD_BLOCKDEV, constants.LD_RBD]:
672
      result = [node]
673
    elif self.dev_type in constants.LDS_DRBD:
674
      result = [self.logical_id[0], self.logical_id[1]]
675
      if node not in result:
676
        raise errors.ConfigurationError("DRBD device passed unknown node")
677
    else:
678
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
679
    return result
680

    
681
  def ComputeNodeTree(self, parent_node):
682
    """Compute the node/disk tree for this disk and its children.
683

684
    This method, given the node on which the parent disk lives, will
685
    return the list of all (node, disk) pairs which describe the disk
686
    tree in the most compact way. For example, a drbd/lvm stack
687
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
688
    which represents all the top-level devices on the nodes.
689

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

    
716
  def ComputeGrowth(self, amount):
717
    """Compute the per-VG growth requirements.
718

719
    This only works for VG-based disks.
720

721
    @type amount: integer
722
    @param amount: the desired increase in (user-visible) disk space
723
    @rtype: dict
724
    @return: a dictionary of volume-groups and the required size
725

726
    """
727
    if self.dev_type == constants.LD_LV:
728
      return {self.logical_id[0]: amount}
729
    elif self.dev_type == constants.LD_DRBD8:
730
      if self.children:
731
        return self.children[0].ComputeGrowth(amount)
732
      else:
733
        return {}
734
    else:
735
      # Other disk types do not require VG space
736
      return {}
737

    
738
  def RecordGrow(self, amount):
739
    """Update the size of this disk after growth.
740

741
    This method recurses over the disks's children and updates their
742
    size correspondigly. The method needs to be kept in sync with the
743
    actual algorithms from bdev.
744

745
    """
746
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
747
                         constants.LD_RBD):
748
      self.size += amount
749
    elif self.dev_type == constants.LD_DRBD8:
750
      if self.children:
751
        self.children[0].RecordGrow(amount)
752
      self.size += amount
753
    else:
754
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
755
                                   " disk type %s" % self.dev_type)
756

    
757
  def Update(self, size=None, mode=None):
758
    """Apply changes to size and mode.
759

760
    """
761
    if self.dev_type == constants.LD_DRBD8:
762
      if self.children:
763
        self.children[0].Update(size=size, mode=mode)
764
    else:
765
      assert not self.children
766

    
767
    if size is not None:
768
      self.size = size
769
    if mode is not None:
770
      self.mode = mode
771

    
772
  def UnsetSize(self):
773
    """Sets recursively the size to zero for the disk and its children.
774

775
    """
776
    if self.children:
777
      for child in self.children:
778
        child.UnsetSize()
779
    self.size = 0
780

    
781
  def SetPhysicalID(self, target_node, nodes_ip):
782
    """Convert the logical ID to the physical ID.
783

784
    This is used only for drbd, which needs ip/port configuration.
785

786
    The routine descends down and updates its children also, because
787
    this helps when the only the top device is passed to the remote
788
    node.
789

790
    Arguments:
791
      - target_node: the node we wish to configure for
792
      - nodes_ip: a mapping of node name to ip
793

794
    The target_node must exist in in nodes_ip, and must be one of the
795
    nodes in the logical ID for each of the DRBD devices encountered
796
    in the disk tree.
797

798
    """
799
    if self.children:
800
      for child in self.children:
801
        child.SetPhysicalID(target_node, nodes_ip)
802

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

    
825
  def ToDict(self):
826
    """Disk-specific conversion to standard python types.
827

828
    This replaces the children lists of objects with lists of
829
    standard python types.
830

831
    """
832
    bo = super(Disk, self).ToDict()
833

    
834
    for attr in ("children",):
835
      alist = bo.get(attr, None)
836
      if alist:
837
        bo[attr] = self._ContainerToDicts(alist)
838
    return bo
839

    
840
  @classmethod
841
  def FromDict(cls, val):
842
    """Custom function for Disks
843

844
    """
845
    obj = super(Disk, cls).FromDict(val)
846
    if obj.children:
847
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
848
    if obj.logical_id and isinstance(obj.logical_id, list):
849
      obj.logical_id = tuple(obj.logical_id)
850
    if obj.physical_id and isinstance(obj.physical_id, list):
851
      obj.physical_id = tuple(obj.physical_id)
852
    if obj.dev_type in constants.LDS_DRBD:
853
      # we need a tuple of length six here
854
      if len(obj.logical_id) < 6:
855
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
856
    return obj
857

    
858
  def __str__(self):
859
    """Custom str() formatter for disks.
860

861
    """
862
    if self.dev_type == constants.LD_LV:
863
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
864
    elif self.dev_type in constants.LDS_DRBD:
865
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
866
      val = "<DRBD8("
867
      if self.physical_id is None:
868
        phy = "unconfigured"
869
      else:
870
        phy = ("configured as %s:%s %s:%s" %
871
               (self.physical_id[0], self.physical_id[1],
872
                self.physical_id[2], self.physical_id[3]))
873

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

    
893
  def Verify(self):
894
    """Checks that this disk is correctly configured.
895

896
    """
897
    all_errors = []
898
    if self.mode not in constants.DISK_ACCESS_SET:
899
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
900
    return all_errors
901

    
902
  def UpgradeConfig(self):
903
    """Fill defaults for missing configuration values.
904

905
    """
906
    if self.children:
907
      for child in self.children:
908
        child.UpgradeConfig()
909

    
910
    if not self.params:
911
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
912
    else:
913
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
914
                             self.params)
915
    # add here config upgrade for this disk
916

    
917

    
918
class InstancePolicy(ConfigObject):
919
  """Config object representing instance policy limits dictionary.
920

921

922
  Note that this object is not actually used in the config, it's just
923
  used as a placeholder for a few functions.
924

925
  """
926
  @classmethod
927
  def CheckParameterSyntax(cls, ipolicy):
928
    """ Check the instance policy for validity.
929

930
    """
931
    for param in constants.ISPECS_PARAMETERS:
932
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
933
    if constants.IPOLICY_DTS in ipolicy:
934
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
935
    for key in constants.IPOLICY_PARAMETERS:
936
      if key in ipolicy:
937
        InstancePolicy.CheckParameter(key, ipolicy[key])
938
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
939
    if wrong_keys:
940
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
941
                                      utils.CommaJoin(wrong_keys))
942

    
943
  @classmethod
944
  def CheckISpecSyntax(cls, ipolicy, name):
945
    """Check the instance policy for validity on a given key.
946

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

950
    @type ipolicy: dict
951
    @param ipolicy: dictionary with min, max, std specs
952
    @type name: string
953
    @param name: what are the limits for
954
    @raise errors.ConfigureError: when specs for given name are not valid
955

956
    """
957
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
958
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
959
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
960
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
961
           (name,
962
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
963
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
964
            ipolicy[constants.ISPECS_STD].get(name, "-")))
965
    if min_v > std_v or std_v > max_v:
966
      raise errors.ConfigurationError(err)
967

    
968
  @classmethod
969
  def CheckDiskTemplates(cls, disk_templates):
970
    """Checks the disk templates for validity.
971

972
    """
973
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
974
    if wrong:
975
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
976
                                      utils.CommaJoin(wrong))
977

    
978
  @classmethod
979
  def CheckParameter(cls, key, value):
980
    """Checks a parameter.
981

982
    Currently we expect all parameters to be float values.
983

984
    """
985
    try:
986
      float(value)
987
    except (TypeError, ValueError), err:
988
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
989
                                      " '%s', error: %s" % (key, value, err))
990

    
991

    
992
class Instance(TaggableObject):
993
  """Config object representing an instance."""
994
  __slots__ = [
995
    "name",
996
    "primary_node",
997
    "os",
998
    "hypervisor",
999
    "hvparams",
1000
    "beparams",
1001
    "osparams",
1002
    "admin_state",
1003
    "nics",
1004
    "disks",
1005
    "disk_template",
1006
    "network_port",
1007
    "serial_no",
1008
    ] + _TIMESTAMPS + _UUID
1009

    
1010
  def _ComputeSecondaryNodes(self):
1011
    """Compute the list of secondary nodes.
1012

1013
    This is a simple wrapper over _ComputeAllNodes.
1014

1015
    """
1016
    all_nodes = set(self._ComputeAllNodes())
1017
    all_nodes.discard(self.primary_node)
1018
    return tuple(all_nodes)
1019

    
1020
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1021
                             "List of secondary nodes")
1022

    
1023
  def _ComputeAllNodes(self):
1024
    """Compute the list of all nodes.
1025

1026
    Since the data is already there (in the drbd disks), keeping it as
1027
    a separate normal attribute is redundant and if not properly
1028
    synchronised can cause problems. Thus it's better to compute it
1029
    dynamically.
1030

1031
    """
1032
    def _Helper(nodes, device):
1033
      """Recursively computes nodes given a top device."""
1034
      if device.dev_type in constants.LDS_DRBD:
1035
        nodea, nodeb = device.logical_id[:2]
1036
        nodes.add(nodea)
1037
        nodes.add(nodeb)
1038
      if device.children:
1039
        for child in device.children:
1040
          _Helper(nodes, child)
1041

    
1042
    all_nodes = set()
1043
    all_nodes.add(self.primary_node)
1044
    for device in self.disks:
1045
      _Helper(all_nodes, device)
1046
    return tuple(all_nodes)
1047

    
1048
  all_nodes = property(_ComputeAllNodes, None, None,
1049
                       "List of all nodes of the instance")
1050

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

1054
    This function figures out what logical volumes should belong on
1055
    which nodes, recursing through a device tree.
1056

1057
    @param lvmap: optional dictionary to receive the
1058
        'node' : ['lv', ...] data.
1059

1060
    @return: None if lvmap arg is given, otherwise, a dictionary of
1061
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1062
        volumeN is of the form "vg_name/lv_name", compatible with
1063
        GetVolumeList()
1064

1065
    """
1066
    if node == None:
1067
      node = self.primary_node
1068

    
1069
    if lvmap is None:
1070
      lvmap = {
1071
        node: [],
1072
        }
1073
      ret = lvmap
1074
    else:
1075
      if not node in lvmap:
1076
        lvmap[node] = []
1077
      ret = None
1078

    
1079
    if not devs:
1080
      devs = self.disks
1081

    
1082
    for dev in devs:
1083
      if dev.dev_type == constants.LD_LV:
1084
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1085

    
1086
      elif dev.dev_type in constants.LDS_DRBD:
1087
        if dev.children:
1088
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1089
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1090

    
1091
      elif dev.children:
1092
        self.MapLVsByNode(lvmap, dev.children, node)
1093

    
1094
    return ret
1095

    
1096
  def FindDisk(self, idx):
1097
    """Find a disk given having a specified index.
1098

1099
    This is just a wrapper that does validation of the index.
1100

1101
    @type idx: int
1102
    @param idx: the disk index
1103
    @rtype: L{Disk}
1104
    @return: the corresponding disk
1105
    @raise errors.OpPrereqError: when the given index is not valid
1106

1107
    """
1108
    try:
1109
      idx = int(idx)
1110
      return self.disks[idx]
1111
    except (TypeError, ValueError), err:
1112
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1113
                                 errors.ECODE_INVAL)
1114
    except IndexError:
1115
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1116
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1117
                                 errors.ECODE_INVAL)
1118

    
1119
  def ToDict(self):
1120
    """Instance-specific conversion to standard python types.
1121

1122
    This replaces the children lists of objects with lists of standard
1123
    python types.
1124

1125
    """
1126
    bo = super(Instance, self).ToDict()
1127

    
1128
    for attr in "nics", "disks":
1129
      alist = bo.get(attr, None)
1130
      if alist:
1131
        nlist = self._ContainerToDicts(alist)
1132
      else:
1133
        nlist = []
1134
      bo[attr] = nlist
1135
    return bo
1136

    
1137
  @classmethod
1138
  def FromDict(cls, val):
1139
    """Custom function for instances.
1140

1141
    """
1142
    if "admin_state" not in val:
1143
      if val.get("admin_up", False):
1144
        val["admin_state"] = constants.ADMINST_UP
1145
      else:
1146
        val["admin_state"] = constants.ADMINST_DOWN
1147
    if "admin_up" in val:
1148
      del val["admin_up"]
1149
    obj = super(Instance, cls).FromDict(val)
1150
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1151
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1152
    return obj
1153

    
1154
  def UpgradeConfig(self):
1155
    """Fill defaults for missing configuration values.
1156

1157
    """
1158
    for nic in self.nics:
1159
      nic.UpgradeConfig()
1160
    for disk in self.disks:
1161
      disk.UpgradeConfig()
1162
    if self.hvparams:
1163
      for key in constants.HVC_GLOBALS:
1164
        try:
1165
          del self.hvparams[key]
1166
        except KeyError:
1167
          pass
1168
    if self.osparams is None:
1169
      self.osparams = {}
1170
    UpgradeBeParams(self.beparams)
1171

    
1172

    
1173
class OS(ConfigObject):
1174
  """Config object representing an operating system.
1175

1176
  @type supported_parameters: list
1177
  @ivar supported_parameters: a list of tuples, name and description,
1178
      containing the supported parameters by this OS
1179

1180
  @type VARIANT_DELIM: string
1181
  @cvar VARIANT_DELIM: the variant delimiter
1182

1183
  """
1184
  __slots__ = [
1185
    "name",
1186
    "path",
1187
    "api_versions",
1188
    "create_script",
1189
    "export_script",
1190
    "import_script",
1191
    "rename_script",
1192
    "verify_script",
1193
    "supported_variants",
1194
    "supported_parameters",
1195
    ]
1196

    
1197
  VARIANT_DELIM = "+"
1198

    
1199
  @classmethod
1200
  def SplitNameVariant(cls, name):
1201
    """Splits the name into the proper name and variant.
1202

1203
    @param name: the OS (unprocessed) name
1204
    @rtype: list
1205
    @return: a list of two elements; if the original name didn't
1206
        contain a variant, it's returned as an empty string
1207

1208
    """
1209
    nv = name.split(cls.VARIANT_DELIM, 1)
1210
    if len(nv) == 1:
1211
      nv.append("")
1212
    return nv
1213

    
1214
  @classmethod
1215
  def GetName(cls, name):
1216
    """Returns the proper name of the os (without the variant).
1217

1218
    @param name: the OS (unprocessed) name
1219

1220
    """
1221
    return cls.SplitNameVariant(name)[0]
1222

    
1223
  @classmethod
1224
  def GetVariant(cls, name):
1225
    """Returns the variant the os (without the base name).
1226

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

1229
    """
1230
    return cls.SplitNameVariant(name)[1]
1231

    
1232

    
1233
class NodeHvState(ConfigObject):
1234
  """Hypvervisor state on a node.
1235

1236
  @ivar mem_total: Total amount of memory
1237
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1238
    available)
1239
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1240
    rounding
1241
  @ivar mem_inst: Memory used by instances living on node
1242
  @ivar cpu_total: Total node CPU core count
1243
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1244

1245
  """
1246
  __slots__ = [
1247
    "mem_total",
1248
    "mem_node",
1249
    "mem_hv",
1250
    "mem_inst",
1251
    "cpu_total",
1252
    "cpu_node",
1253
    ] + _TIMESTAMPS
1254

    
1255

    
1256
class NodeDiskState(ConfigObject):
1257
  """Disk state on a node.
1258

1259
  """
1260
  __slots__ = [
1261
    "total",
1262
    "reserved",
1263
    "overhead",
1264
    ] + _TIMESTAMPS
1265

    
1266

    
1267
class Node(TaggableObject):
1268
  """Config object representing a node.
1269

1270
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1271
  @ivar hv_state_static: Hypervisor state overriden by user
1272
  @ivar disk_state: Disk state (e.g. free space)
1273
  @ivar disk_state_static: Disk state overriden by user
1274

1275
  """
1276
  __slots__ = [
1277
    "name",
1278
    "primary_ip",
1279
    "secondary_ip",
1280
    "serial_no",
1281
    "master_candidate",
1282
    "offline",
1283
    "drained",
1284
    "group",
1285
    "master_capable",
1286
    "vm_capable",
1287
    "ndparams",
1288
    "powered",
1289
    "hv_state",
1290
    "hv_state_static",
1291
    "disk_state",
1292
    "disk_state_static",
1293
    ] + _TIMESTAMPS + _UUID
1294

    
1295
  def UpgradeConfig(self):
1296
    """Fill defaults for missing configuration values.
1297

1298
    """
1299
    # pylint: disable=E0203
1300
    # because these are "defined" via slots, not manually
1301
    if self.master_capable is None:
1302
      self.master_capable = True
1303

    
1304
    if self.vm_capable is None:
1305
      self.vm_capable = True
1306

    
1307
    if self.ndparams is None:
1308
      self.ndparams = {}
1309

    
1310
    if self.powered is None:
1311
      self.powered = True
1312

    
1313
  def ToDict(self):
1314
    """Custom function for serializing.
1315

1316
    """
1317
    data = super(Node, self).ToDict()
1318

    
1319
    hv_state = data.get("hv_state", None)
1320
    if hv_state is not None:
1321
      data["hv_state"] = self._ContainerToDicts(hv_state)
1322

    
1323
    disk_state = data.get("disk_state", None)
1324
    if disk_state is not None:
1325
      data["disk_state"] = \
1326
        dict((key, self._ContainerToDicts(value))
1327
             for (key, value) in disk_state.items())
1328

    
1329
    return data
1330

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

1335
    """
1336
    obj = super(Node, cls).FromDict(val)
1337

    
1338
    if obj.hv_state is not None:
1339
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1340

    
1341
    if obj.disk_state is not None:
1342
      obj.disk_state = \
1343
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1344
             for (key, value) in obj.disk_state.items())
1345

    
1346
    return obj
1347

    
1348

    
1349
class NodeGroup(TaggableObject):
1350
  """Config object representing a node group."""
1351
  __slots__ = [
1352
    "name",
1353
    "members",
1354
    "ndparams",
1355
    "diskparams",
1356
    "ipolicy",
1357
    "serial_no",
1358
    "hv_state_static",
1359
    "disk_state_static",
1360
    "alloc_policy",
1361
    ] + _TIMESTAMPS + _UUID
1362

    
1363
  def ToDict(self):
1364
    """Custom function for nodegroup.
1365

1366
    This discards the members object, which gets recalculated and is only kept
1367
    in memory.
1368

1369
    """
1370
    mydict = super(NodeGroup, self).ToDict()
1371
    del mydict["members"]
1372
    return mydict
1373

    
1374
  @classmethod
1375
  def FromDict(cls, val):
1376
    """Custom function for nodegroup.
1377

1378
    The members slot is initialized to an empty list, upon deserialization.
1379

1380
    """
1381
    obj = super(NodeGroup, cls).FromDict(val)
1382
    obj.members = []
1383
    return obj
1384

    
1385
  def UpgradeConfig(self):
1386
    """Fill defaults for missing configuration values.
1387

1388
    """
1389
    if self.ndparams is None:
1390
      self.ndparams = {}
1391

    
1392
    if self.serial_no is None:
1393
      self.serial_no = 1
1394

    
1395
    if self.alloc_policy is None:
1396
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1397

    
1398
    # We only update mtime, and not ctime, since we would not be able
1399
    # to provide a correct value for creation time.
1400
    if self.mtime is None:
1401
      self.mtime = time.time()
1402

    
1403
    self.diskparams = UpgradeDiskParams(self.diskparams)
1404
    if self.ipolicy is None:
1405
      self.ipolicy = MakeEmptyIPolicy()
1406

    
1407
  def FillND(self, node):
1408
    """Return filled out ndparams for L{objects.Node}
1409

1410
    @type node: L{objects.Node}
1411
    @param node: A Node object to fill
1412
    @return a copy of the node's ndparams with defaults filled
1413

1414
    """
1415
    return self.SimpleFillND(node.ndparams)
1416

    
1417
  def SimpleFillND(self, ndparams):
1418
    """Fill a given ndparams dict with defaults.
1419

1420
    @type ndparams: dict
1421
    @param ndparams: the dict to fill
1422
    @rtype: dict
1423
    @return: a copy of the passed in ndparams with missing keys filled
1424
        from the node group defaults
1425

1426
    """
1427
    return FillDict(self.ndparams, ndparams)
1428

    
1429

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

    
1474
  def UpgradeConfig(self):
1475
    """Fill defaults for missing configuration values.
1476

1477
    """
1478
    # pylint: disable=E0203
1479
    # because these are "defined" via slots, not manually
1480
    if self.hvparams is None:
1481
      self.hvparams = constants.HVC_DEFAULTS
1482
    else:
1483
      for hypervisor in self.hvparams:
1484
        self.hvparams[hypervisor] = FillDict(
1485
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1486

    
1487
    if self.os_hvp is None:
1488
      self.os_hvp = {}
1489

    
1490
    # osparams added before 2.2
1491
    if self.osparams is None:
1492
      self.osparams = {}
1493

    
1494
    self.ndparams = UpgradeNDParams(self.ndparams)
1495

    
1496
    self.beparams = UpgradeGroupedParams(self.beparams,
1497
                                         constants.BEC_DEFAULTS)
1498
    for beparams_group in self.beparams:
1499
      UpgradeBeParams(self.beparams[beparams_group])
1500

    
1501
    migrate_default_bridge = not self.nicparams
1502
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1503
                                          constants.NICC_DEFAULTS)
1504
    if migrate_default_bridge:
1505
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1506
        self.default_bridge
1507

    
1508
    if self.modify_etc_hosts is None:
1509
      self.modify_etc_hosts = True
1510

    
1511
    if self.modify_ssh_setup is None:
1512
      self.modify_ssh_setup = True
1513

    
1514
    # default_bridge is no longer used in 2.1. The slot is left there to
1515
    # support auto-upgrading. It can be removed once we decide to deprecate
1516
    # upgrading straight from 2.0.
1517
    if self.default_bridge is not None:
1518
      self.default_bridge = None
1519

    
1520
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1521
    # code can be removed once upgrading straight from 2.0 is deprecated.
1522
    if self.default_hypervisor is not None:
1523
      self.enabled_hypervisors = ([self.default_hypervisor] +
1524
        [hvname for hvname in self.enabled_hypervisors
1525
         if hvname != self.default_hypervisor])
1526
      self.default_hypervisor = None
1527

    
1528
    # maintain_node_health added after 2.1.1
1529
    if self.maintain_node_health is None:
1530
      self.maintain_node_health = False
1531

    
1532
    if self.uid_pool is None:
1533
      self.uid_pool = []
1534

    
1535
    if self.default_iallocator is None:
1536
      self.default_iallocator = ""
1537

    
1538
    # reserved_lvs added before 2.2
1539
    if self.reserved_lvs is None:
1540
      self.reserved_lvs = []
1541

    
1542
    # hidden and blacklisted operating systems added before 2.2.1
1543
    if self.hidden_os is None:
1544
      self.hidden_os = []
1545

    
1546
    if self.blacklisted_os is None:
1547
      self.blacklisted_os = []
1548

    
1549
    # primary_ip_family added before 2.3
1550
    if self.primary_ip_family is None:
1551
      self.primary_ip_family = AF_INET
1552

    
1553
    if self.master_netmask is None:
1554
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1555
      self.master_netmask = ipcls.iplen
1556

    
1557
    if self.prealloc_wipe_disks is None:
1558
      self.prealloc_wipe_disks = False
1559

    
1560
    # shared_file_storage_dir added before 2.5
1561
    if self.shared_file_storage_dir is None:
1562
      self.shared_file_storage_dir = ""
1563

    
1564
    if self.use_external_mip_script is None:
1565
      self.use_external_mip_script = False
1566

    
1567
    self.diskparams = UpgradeDiskParams(self.diskparams)
1568

    
1569
    # instance policy added before 2.6
1570
    if self.ipolicy is None:
1571
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1572
    else:
1573
      # we can either make sure to upgrade the ipolicy always, or only
1574
      # do it in some corner cases (e.g. missing keys); note that this
1575
      # will break any removal of keys from the ipolicy dict
1576
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1577

    
1578
  @property
1579
  def primary_hypervisor(self):
1580
    """The first hypervisor is the primary.
1581

1582
    Useful, for example, for L{Node}'s hv/disk state.
1583

1584
    """
1585
    return self.enabled_hypervisors[0]
1586

    
1587
  def ToDict(self):
1588
    """Custom function for cluster.
1589

1590
    """
1591
    mydict = super(Cluster, self).ToDict()
1592
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1593
    return mydict
1594

    
1595
  @classmethod
1596
  def FromDict(cls, val):
1597
    """Custom function for cluster.
1598

1599
    """
1600
    obj = super(Cluster, cls).FromDict(val)
1601
    if not isinstance(obj.tcpudp_port_pool, set):
1602
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1603
    return obj
1604

    
1605
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1606
    """Get the default hypervisor parameters for the cluster.
1607

1608
    @param hypervisor: the hypervisor name
1609
    @param os_name: if specified, we'll also update the defaults for this OS
1610
    @param skip_keys: if passed, list of keys not to use
1611
    @return: the defaults dict
1612

1613
    """
1614
    if skip_keys is None:
1615
      skip_keys = []
1616

    
1617
    fill_stack = [self.hvparams.get(hypervisor, {})]
1618
    if os_name is not None:
1619
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1620
      fill_stack.append(os_hvp)
1621

    
1622
    ret_dict = {}
1623
    for o_dict in fill_stack:
1624
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1625

    
1626
    return ret_dict
1627

    
1628
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1629
    """Fill a given hvparams dict with cluster defaults.
1630

1631
    @type hv_name: string
1632
    @param hv_name: the hypervisor to use
1633
    @type os_name: string
1634
    @param os_name: the OS to use for overriding the hypervisor defaults
1635
    @type skip_globals: boolean
1636
    @param skip_globals: if True, the global hypervisor parameters will
1637
        not be filled
1638
    @rtype: dict
1639
    @return: a copy of the given hvparams with missing keys filled from
1640
        the cluster defaults
1641

1642
    """
1643
    if skip_globals:
1644
      skip_keys = constants.HVC_GLOBALS
1645
    else:
1646
      skip_keys = []
1647

    
1648
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1649
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1650

    
1651
  def FillHV(self, instance, skip_globals=False):
1652
    """Fill an instance's hvparams dict with cluster defaults.
1653

1654
    @type instance: L{objects.Instance}
1655
    @param instance: the instance parameter to fill
1656
    @type skip_globals: boolean
1657
    @param skip_globals: if True, the global hypervisor parameters will
1658
        not be filled
1659
    @rtype: dict
1660
    @return: a copy of the instance's hvparams with missing keys filled from
1661
        the cluster defaults
1662

1663
    """
1664
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1665
                             instance.hvparams, skip_globals)
1666

    
1667
  def SimpleFillBE(self, beparams):
1668
    """Fill a given beparams dict with cluster defaults.
1669

1670
    @type beparams: dict
1671
    @param beparams: the dict to fill
1672
    @rtype: dict
1673
    @return: a copy of the passed in beparams with missing keys filled
1674
        from the cluster defaults
1675

1676
    """
1677
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1678

    
1679
  def FillBE(self, instance):
1680
    """Fill an instance's beparams dict with cluster defaults.
1681

1682
    @type instance: L{objects.Instance}
1683
    @param instance: the instance parameter to fill
1684
    @rtype: dict
1685
    @return: a copy of the instance's beparams with missing keys filled from
1686
        the cluster defaults
1687

1688
    """
1689
    return self.SimpleFillBE(instance.beparams)
1690

    
1691
  def SimpleFillNIC(self, nicparams):
1692
    """Fill a given nicparams dict with cluster defaults.
1693

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

1700
    """
1701
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1702

    
1703
  def SimpleFillOS(self, os_name, os_params):
1704
    """Fill an instance's osparams dict with cluster defaults.
1705

1706
    @type os_name: string
1707
    @param os_name: the OS name to use
1708
    @type os_params: dict
1709
    @param os_params: the dict to fill with default values
1710
    @rtype: dict
1711
    @return: a copy of the instance's osparams with missing keys filled from
1712
        the cluster defaults
1713

1714
    """
1715
    name_only = os_name.split("+", 1)[0]
1716
    # base OS
1717
    result = self.osparams.get(name_only, {})
1718
    # OS with variant
1719
    result = FillDict(result, self.osparams.get(os_name, {}))
1720
    # specified params
1721
    return FillDict(result, os_params)
1722

    
1723
  @staticmethod
1724
  def SimpleFillHvState(hv_state):
1725
    """Fill an hv_state sub dict with cluster defaults.
1726

1727
    """
1728
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1729

    
1730
  @staticmethod
1731
  def SimpleFillDiskState(disk_state):
1732
    """Fill an disk_state sub dict with cluster defaults.
1733

1734
    """
1735
    return FillDict(constants.DS_DEFAULTS, disk_state)
1736

    
1737
  def FillND(self, node, nodegroup):
1738
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1739

1740
    @type node: L{objects.Node}
1741
    @param node: A Node object to fill
1742
    @type nodegroup: L{objects.NodeGroup}
1743
    @param nodegroup: A Node object to fill
1744
    @return a copy of the node's ndparams with defaults filled
1745

1746
    """
1747
    return self.SimpleFillND(nodegroup.FillND(node))
1748

    
1749
  def SimpleFillND(self, ndparams):
1750
    """Fill a given ndparams dict with defaults.
1751

1752
    @type ndparams: dict
1753
    @param ndparams: the dict to fill
1754
    @rtype: dict
1755
    @return: a copy of the passed in ndparams with missing keys filled
1756
        from the cluster defaults
1757

1758
    """
1759
    return FillDict(self.ndparams, ndparams)
1760

    
1761
  def SimpleFillIPolicy(self, ipolicy):
1762
    """ Fill instance policy dict with defaults.
1763

1764
    @type ipolicy: dict
1765
    @param ipolicy: the dict to fill
1766
    @rtype: dict
1767
    @return: a copy of passed ipolicy with missing keys filled from
1768
      the cluster defaults
1769

1770
    """
1771
    return FillIPolicy(self.ipolicy, ipolicy)
1772

    
1773

    
1774
class BlockDevStatus(ConfigObject):
1775
  """Config object representing the status of a block device."""
1776
  __slots__ = [
1777
    "dev_path",
1778
    "major",
1779
    "minor",
1780
    "sync_percent",
1781
    "estimated_time",
1782
    "is_degraded",
1783
    "ldisk_status",
1784
    ]
1785

    
1786

    
1787
class ImportExportStatus(ConfigObject):
1788
  """Config object representing the status of an import or export."""
1789
  __slots__ = [
1790
    "recent_output",
1791
    "listen_port",
1792
    "connected",
1793
    "progress_mbytes",
1794
    "progress_throughput",
1795
    "progress_eta",
1796
    "progress_percent",
1797
    "exit_status",
1798
    "error_message",
1799
    ] + _TIMESTAMPS
1800

    
1801

    
1802
class ImportExportOptions(ConfigObject):
1803
  """Options for import/export daemon
1804

1805
  @ivar key_name: X509 key name (None for cluster certificate)
1806
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1807
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1808
  @ivar magic: Used to ensure the connection goes to the right disk
1809
  @ivar ipv6: Whether to use IPv6
1810
  @ivar connect_timeout: Number of seconds for establishing connection
1811

1812
  """
1813
  __slots__ = [
1814
    "key_name",
1815
    "ca_pem",
1816
    "compress",
1817
    "magic",
1818
    "ipv6",
1819
    "connect_timeout",
1820
    ]
1821

    
1822

    
1823
class ConfdRequest(ConfigObject):
1824
  """Object holding a confd request.
1825

1826
  @ivar protocol: confd protocol version
1827
  @ivar type: confd query type
1828
  @ivar query: query request
1829
  @ivar rsalt: requested reply salt
1830

1831
  """
1832
  __slots__ = [
1833
    "protocol",
1834
    "type",
1835
    "query",
1836
    "rsalt",
1837
    ]
1838

    
1839

    
1840
class ConfdReply(ConfigObject):
1841
  """Object holding a confd reply.
1842

1843
  @ivar protocol: confd protocol version
1844
  @ivar status: reply status code (ok, error)
1845
  @ivar answer: confd query reply
1846
  @ivar serial: configuration serial number
1847

1848
  """
1849
  __slots__ = [
1850
    "protocol",
1851
    "status",
1852
    "answer",
1853
    "serial",
1854
    ]
1855

    
1856

    
1857
class QueryFieldDefinition(ConfigObject):
1858
  """Object holding a query field definition.
1859

1860
  @ivar name: Field name
1861
  @ivar title: Human-readable title
1862
  @ivar kind: Field type
1863
  @ivar doc: Human-readable description
1864

1865
  """
1866
  __slots__ = [
1867
    "name",
1868
    "title",
1869
    "kind",
1870
    "doc",
1871
    ]
1872

    
1873

    
1874
class _QueryResponseBase(ConfigObject):
1875
  __slots__ = [
1876
    "fields",
1877
    ]
1878

    
1879
  def ToDict(self):
1880
    """Custom function for serializing.
1881

1882
    """
1883
    mydict = super(_QueryResponseBase, self).ToDict()
1884
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1885
    return mydict
1886

    
1887
  @classmethod
1888
  def FromDict(cls, val):
1889
    """Custom function for de-serializing.
1890

1891
    """
1892
    obj = super(_QueryResponseBase, cls).FromDict(val)
1893
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1894
    return obj
1895

    
1896

    
1897
class QueryRequest(ConfigObject):
1898
  """Object holding a query request.
1899

1900
  """
1901
  __slots__ = [
1902
    "what",
1903
    "fields",
1904
    "qfilter",
1905
    ]
1906

    
1907

    
1908
class QueryResponse(_QueryResponseBase):
1909
  """Object holding the response to a query.
1910

1911
  @ivar fields: List of L{QueryFieldDefinition} objects
1912
  @ivar data: Requested data
1913

1914
  """
1915
  __slots__ = [
1916
    "data",
1917
    ]
1918

    
1919

    
1920
class QueryFieldsRequest(ConfigObject):
1921
  """Object holding a request for querying available fields.
1922

1923
  """
1924
  __slots__ = [
1925
    "what",
1926
    "fields",
1927
    ]
1928

    
1929

    
1930
class QueryFieldsResponse(_QueryResponseBase):
1931
  """Object holding the response to a query for fields.
1932

1933
  @ivar fields: List of L{QueryFieldDefinition} objects
1934

1935
  """
1936
  __slots__ = [
1937
    ]
1938

    
1939

    
1940
class MigrationStatus(ConfigObject):
1941
  """Object holding the status of a migration.
1942

1943
  """
1944
  __slots__ = [
1945
    "status",
1946
    "transferred_ram",
1947
    "total_ram",
1948
    ]
1949

    
1950

    
1951
class InstanceConsole(ConfigObject):
1952
  """Object describing how to access the console of an instance.
1953

1954
  """
1955
  __slots__ = [
1956
    "instance",
1957
    "kind",
1958
    "message",
1959
    "host",
1960
    "port",
1961
    "user",
1962
    "command",
1963
    "display",
1964
    ]
1965

    
1966
  def Validate(self):
1967
    """Validates contents of this object.
1968

1969
    """
1970
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1971
    assert self.instance, "Missing instance name"
1972
    assert self.message or self.kind in [constants.CONS_SSH,
1973
                                         constants.CONS_SPICE,
1974
                                         constants.CONS_VNC]
1975
    assert self.host or self.kind == constants.CONS_MESSAGE
1976
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1977
                                      constants.CONS_SSH]
1978
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1979
                                      constants.CONS_SPICE,
1980
                                      constants.CONS_VNC]
1981
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1982
                                         constants.CONS_SPICE,
1983
                                         constants.CONS_VNC]
1984
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1985
                                         constants.CONS_SPICE,
1986
                                         constants.CONS_SSH]
1987
    return True
1988

    
1989

    
1990
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1991
  """Simple wrapper over ConfigParse that allows serialization.
1992

1993
  This class is basically ConfigParser.SafeConfigParser with two
1994
  additional methods that allow it to serialize/unserialize to/from a
1995
  buffer.
1996

1997
  """
1998
  def Dumps(self):
1999
    """Dump this instance and return the string representation."""
2000
    buf = StringIO()
2001
    self.write(buf)
2002
    return buf.getvalue()
2003

    
2004
  @classmethod
2005
  def Loads(cls, data):
2006
    """Load data from a string."""
2007
    buf = StringIO(data)
2008
    cfp = cls()
2009
    cfp.readfp(buf)
2010
    return cfp