Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 81e3ab4f

History | View | Annotate | Download (53.3 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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.MIN_ISPECS: constants.VTYPE_INT,
61
  constants.MAX_ISPECS: constants.VTYPE_INT,
62
}
63

    
64
TISPECS_CLUSTER_TYPES = {
65
  constants.MIN_ISPECS: constants.VTYPE_INT,
66
  constants.MAX_ISPECS: constants.VTYPE_INT,
67
  constants.STD_ISPECS: 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 FillDictOfDicts(defaults_dict, custom_dict, skip_keys=None):
96
  """Run FillDict for each key in dictionary.
97

98
  """
99
  ret_dict = {}
100
  for key in defaults_dict.keys():
101
    ret_dict[key] = FillDict(defaults_dict[key],
102
                             custom_dict.get(key, {}),
103
                             skip_keys=skip_keys)
104
  return ret_dict
105

    
106

    
107
def UpgradeGroupedParams(target, defaults):
108
  """Update all groups for the target parameter.
109

110
  @type target: dict of dicts
111
  @param target: {group: {parameter: value}}
112
  @type defaults: dict
113
  @param defaults: default parameter values
114

115
  """
116
  if target is None:
117
    target = {constants.PP_DEFAULT: defaults}
118
  else:
119
    for group in target:
120
      target[group] = FillDict(defaults, target[group])
121
  return target
122

    
123

    
124
def UpgradeBeParams(target):
125
  """Update the be parameters dict to the new format.
126

127
  @type target: dict
128
  @param target: "be" parameters dict
129

130
  """
131
  if constants.BE_MEMORY in target:
132
    memory = target[constants.BE_MEMORY]
133
    target[constants.BE_MAXMEM] = memory
134
    target[constants.BE_MINMEM] = memory
135
    del target[constants.BE_MEMORY]
136

    
137

    
138
def UpgradeDiskParams(diskparams):
139
  """Upgrade the disk parameters.
140

141
  @type diskparams: dict
142
  @param diskparams: disk parameters to upgrade
143
  @rtype: dict
144
  @return: the upgraded disk parameters dit
145

146
  """
147
  result = dict()
148
  if diskparams is None:
149
    result = constants.DISK_DT_DEFAULTS.copy()
150
  else:
151
    # Update the disk parameter values for each disk template.
152
    # The code iterates over constants.DISK_TEMPLATES because new templates
153
    # might have been added.
154
    for template in constants.DISK_TEMPLATES:
155
      if template not in diskparams:
156
        result[template] = constants.DISK_DT_DEFAULTS[template].copy()
157
      else:
158
        result[template] = FillDict(constants.DISK_DT_DEFAULTS[template],
159
                                    diskparams[template])
160

    
161
  return result
162

    
163

    
164
def MakeEmptyIPolicy():
165
  """Create empty IPolicy dictionary.
166

167
  """
168
  return dict([
169
    (constants.MIN_ISPECS, dict()),
170
    (constants.MAX_ISPECS, dict()),
171
    (constants.STD_ISPECS, dict()),
172
    ])
173

    
174

    
175
def CreateIPolicyFromOpts(ispecs_mem_size=None,
176
                          ispecs_cpu_count=None,
177
                          ispecs_disk_count=None,
178
                          ispecs_disk_size=None,
179
                          ispecs_nic_count=None,
180
                          group_ipolicy=False,
181
                          allowed_values=None):
182
  """Creation of instane policy based on command line options.
183

184

185
  """
186
  # prepare ipolicy dict
187
  ipolicy_transposed = {
188
    constants.MEM_SIZE_SPEC: ispecs_mem_size,
189
    constants.CPU_COUNT_SPEC: ispecs_cpu_count,
190
    constants.DISK_COUNT_SPEC: ispecs_disk_count,
191
    constants.DISK_SIZE_SPEC: ispecs_disk_size,
192
    constants.NIC_COUNT_SPEC: ispecs_nic_count,
193
    }
194

    
195
  # first, check that the values given are correct
196
  if group_ipolicy:
197
    forced_type = TISPECS_GROUP_TYPES
198
  else:
199
    forced_type = TISPECS_CLUSTER_TYPES
200

    
201
  for specs in ipolicy_transposed.values():
202
    utils.ForceDictType(specs, forced_type, allowed_values=allowed_values)
203

    
204
  # then transpose
205
  ipolicy_out = MakeEmptyIPolicy()
206
  for name, specs in ipolicy_transposed.iteritems():
207
    assert name in constants.ISPECS_PARAMETERS
208
    for key, val in specs.items(): # {min: .. ,max: .., std: ..}
209
      ipolicy_out[key][name] = val
210

    
211
  return ipolicy_out
212

    
213

    
214
class ConfigObject(object):
215
  """A generic config object.
216

217
  It has the following properties:
218

219
    - provides somewhat safe recursive unpickling and pickling for its classes
220
    - unset attributes which are defined in slots are always returned
221
      as None instead of raising an error
222

223
  Classes derived from this must always declare __slots__ (we use many
224
  config objects and the memory reduction is useful)
225

226
  """
227
  __slots__ = []
228

    
229
  def __init__(self, **kwargs):
230
    for k, v in kwargs.iteritems():
231
      setattr(self, k, v)
232

    
233
  def __getattr__(self, name):
234
    if name not in self._all_slots():
235
      raise AttributeError("Invalid object attribute %s.%s" %
236
                           (type(self).__name__, name))
237
    return None
238

    
239
  def __setstate__(self, state):
240
    slots = self._all_slots()
241
    for name in state:
242
      if name in slots:
243
        setattr(self, name, state[name])
244

    
245
  @classmethod
246
  def _all_slots(cls):
247
    """Compute the list of all declared slots for a class.
248

249
    """
250
    slots = []
251
    for parent in cls.__mro__:
252
      slots.extend(getattr(parent, "__slots__", []))
253
    return slots
254

    
255
  def ToDict(self):
256
    """Convert to a dict holding only standard python types.
257

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

264
    """
265
    result = {}
266
    for name in self._all_slots():
267
      value = getattr(self, name, None)
268
      if value is not None:
269
        result[name] = value
270
    return result
271

    
272
  __getstate__ = ToDict
273

    
274
  @classmethod
275
  def FromDict(cls, val):
276
    """Create an object from a dictionary.
277

278
    This generic routine takes a dict, instantiates a new instance of
279
    the given class, and sets attributes based on the dict content.
280

281
    As for `ToDict`, this does not work if the class has children
282
    who are ConfigObjects themselves (e.g. the nics list in an
283
    Instance), in which case the object should subclass the function
284
    and alter the objects.
285

286
    """
287
    if not isinstance(val, dict):
288
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
289
                                      " expected dict, got %s" % type(val))
290
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
291
    obj = cls(**val_str) # pylint: disable=W0142
292
    return obj
293

    
294
  @staticmethod
295
  def _ContainerToDicts(container):
296
    """Convert the elements of a container to standard python types.
297

298
    This method converts a container with elements derived from
299
    ConfigData to standard python types. If the container is a dict,
300
    we don't touch the keys, only the values.
301

302
    """
303
    if isinstance(container, dict):
304
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
305
    elif isinstance(container, (list, tuple, set, frozenset)):
306
      ret = [elem.ToDict() for elem in container]
307
    else:
308
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
309
                      type(container))
310
    return ret
311

    
312
  @staticmethod
313
  def _ContainerFromDicts(source, c_type, e_type):
314
    """Convert a container from standard python types.
315

316
    This method converts a container with standard python types to
317
    ConfigData objects. If the container is a dict, we don't touch the
318
    keys, only the values.
319

320
    """
321
    if not isinstance(c_type, type):
322
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
323
                      " not a type" % type(c_type))
324
    if source is None:
325
      source = c_type()
326
    if c_type is dict:
327
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
328
    elif c_type in (list, tuple, set, frozenset):
329
      ret = c_type([e_type.FromDict(elem) for elem in source])
330
    else:
331
      raise TypeError("Invalid container type %s passed to"
332
                      " _ContainerFromDicts" % c_type)
333
    return ret
334

    
335
  def Copy(self):
336
    """Makes a deep copy of the current object and its children.
337

338
    """
339
    dict_form = self.ToDict()
340
    clone_obj = self.__class__.FromDict(dict_form)
341
    return clone_obj
342

    
343
  def __repr__(self):
344
    """Implement __repr__ for ConfigObjects."""
345
    return repr(self.ToDict())
346

    
347
  def UpgradeConfig(self):
348
    """Fill defaults for missing configuration values.
349

350
    This method will be called at configuration load time, and its
351
    implementation will be object dependent.
352

353
    """
354
    pass
355

    
356

    
357
class TaggableObject(ConfigObject):
358
  """An generic class supporting tags.
359

360
  """
361
  __slots__ = ["tags"]
362
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
363

    
364
  @classmethod
365
  def ValidateTag(cls, tag):
366
    """Check if a tag is valid.
367

368
    If the tag is invalid, an errors.TagError will be raised. The
369
    function has no return value.
370

371
    """
372
    if not isinstance(tag, basestring):
373
      raise errors.TagError("Invalid tag type (not a string)")
374
    if len(tag) > constants.MAX_TAG_LEN:
375
      raise errors.TagError("Tag too long (>%d characters)" %
376
                            constants.MAX_TAG_LEN)
377
    if not tag:
378
      raise errors.TagError("Tags cannot be empty")
379
    if not cls.VALID_TAG_RE.match(tag):
380
      raise errors.TagError("Tag contains invalid characters")
381

    
382
  def GetTags(self):
383
    """Return the tags list.
384

385
    """
386
    tags = getattr(self, "tags", None)
387
    if tags is None:
388
      tags = self.tags = set()
389
    return tags
390

    
391
  def AddTag(self, tag):
392
    """Add a new tag.
393

394
    """
395
    self.ValidateTag(tag)
396
    tags = self.GetTags()
397
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
398
      raise errors.TagError("Too many tags")
399
    self.GetTags().add(tag)
400

    
401
  def RemoveTag(self, tag):
402
    """Remove a tag.
403

404
    """
405
    self.ValidateTag(tag)
406
    tags = self.GetTags()
407
    try:
408
      tags.remove(tag)
409
    except KeyError:
410
      raise errors.TagError("Tag not found")
411

    
412
  def ToDict(self):
413
    """Taggable-object-specific conversion to standard python types.
414

415
    This replaces the tags set with a list.
416

417
    """
418
    bo = super(TaggableObject, self).ToDict()
419

    
420
    tags = bo.get("tags", None)
421
    if isinstance(tags, set):
422
      bo["tags"] = list(tags)
423
    return bo
424

    
425
  @classmethod
426
  def FromDict(cls, val):
427
    """Custom function for instances.
428

429
    """
430
    obj = super(TaggableObject, cls).FromDict(val)
431
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
432
      obj.tags = set(obj.tags)
433
    return obj
434

    
435

    
436
class MasterNetworkParameters(ConfigObject):
437
  """Network configuration parameters for the master
438

439
  @ivar name: master name
440
  @ivar ip: master IP
441
  @ivar netmask: master netmask
442
  @ivar netdev: master network device
443
  @ivar ip_family: master IP family
444

445
  """
446
  __slots__ = [
447
    "name",
448
    "ip",
449
    "netmask",
450
    "netdev",
451
    "ip_family"
452
    ]
453

    
454

    
455
class ConfigData(ConfigObject):
456
  """Top-level config object."""
457
  __slots__ = [
458
    "version",
459
    "cluster",
460
    "nodes",
461
    "nodegroups",
462
    "instances",
463
    "serial_no",
464
    ] + _TIMESTAMPS
465

    
466
  def ToDict(self):
467
    """Custom function for top-level config data.
468

469
    This just replaces the list of instances, nodes and the cluster
470
    with standard python types.
471

472
    """
473
    mydict = super(ConfigData, self).ToDict()
474
    mydict["cluster"] = mydict["cluster"].ToDict()
475
    for key in "nodes", "instances", "nodegroups":
476
      mydict[key] = self._ContainerToDicts(mydict[key])
477

    
478
    return mydict
479

    
480
  @classmethod
481
  def FromDict(cls, val):
482
    """Custom function for top-level config data
483

484
    """
485
    obj = super(ConfigData, cls).FromDict(val)
486
    obj.cluster = Cluster.FromDict(obj.cluster)
487
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
488
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
489
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
490
    return obj
491

    
492
  def HasAnyDiskOfType(self, dev_type):
493
    """Check if in there is at disk of the given type in the configuration.
494

495
    @type dev_type: L{constants.LDS_BLOCK}
496
    @param dev_type: the type to look for
497
    @rtype: boolean
498
    @return: boolean indicating if a disk of the given type was found or not
499

500
    """
501
    for instance in self.instances.values():
502
      for disk in instance.disks:
503
        if disk.IsBasedOnDiskType(dev_type):
504
          return True
505
    return False
506

    
507
  def UpgradeConfig(self):
508
    """Fill defaults for missing configuration values.
509

510
    """
511
    self.cluster.UpgradeConfig()
512
    for node in self.nodes.values():
513
      node.UpgradeConfig()
514
    for instance in self.instances.values():
515
      instance.UpgradeConfig()
516
    if self.nodegroups is None:
517
      self.nodegroups = {}
518
    for nodegroup in self.nodegroups.values():
519
      nodegroup.UpgradeConfig()
520
    if self.cluster.drbd_usermode_helper is None:
521
      # To decide if we set an helper let's check if at least one instance has
522
      # a DRBD disk. This does not cover all the possible scenarios but it
523
      # gives a good approximation.
524
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
525
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
526

    
527

    
528
class NIC(ConfigObject):
529
  """Config object representing a network card."""
530
  __slots__ = ["mac", "ip", "nicparams"]
531

    
532
  @classmethod
533
  def CheckParameterSyntax(cls, nicparams):
534
    """Check the given parameters for validity.
535

536
    @type nicparams:  dict
537
    @param nicparams: dictionary with parameter names/value
538
    @raise errors.ConfigurationError: when a parameter is not valid
539

540
    """
541
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
542
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
543
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
544
      raise errors.ConfigurationError(err)
545

    
546
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
547
        not nicparams[constants.NIC_LINK]):
548
      err = "Missing bridged nic link"
549
      raise errors.ConfigurationError(err)
550

    
551

    
552
class Disk(ConfigObject):
553
  """Config object representing a block device."""
554
  __slots__ = ["dev_type", "logical_id", "physical_id",
555
               "children", "iv_name", "size", "mode", "params"]
556

    
557
  def CreateOnSecondary(self):
558
    """Test if this device needs to be created on a secondary node."""
559
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
560

    
561
  def AssembleOnSecondary(self):
562
    """Test if this device needs to be assembled on a secondary node."""
563
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
564

    
565
  def OpenOnSecondary(self):
566
    """Test if this device needs to be opened on a secondary node."""
567
    return self.dev_type in (constants.LD_LV,)
568

    
569
  def StaticDevPath(self):
570
    """Return the device path if this device type has a static one.
571

572
    Some devices (LVM for example) live always at the same /dev/ path,
573
    irrespective of their status. For such devices, we return this
574
    path, for others we return None.
575

576
    @warning: The path returned is not a normalized pathname; callers
577
        should check that it is a valid path.
578

579
    """
580
    if self.dev_type == constants.LD_LV:
581
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
582
    elif self.dev_type == constants.LD_BLOCKDEV:
583
      return self.logical_id[1]
584
    return None
585

    
586
  def ChildrenNeeded(self):
587
    """Compute the needed number of children for activation.
588

589
    This method will return either -1 (all children) or a positive
590
    number denoting the minimum number of children needed for
591
    activation (only mirrored devices will usually return >=0).
592

593
    Currently, only DRBD8 supports diskless activation (therefore we
594
    return 0), for all other we keep the previous semantics and return
595
    -1.
596

597
    """
598
    if self.dev_type == constants.LD_DRBD8:
599
      return 0
600
    return -1
601

    
602
  def IsBasedOnDiskType(self, dev_type):
603
    """Check if the disk or its children are based on the given type.
604

605
    @type dev_type: L{constants.LDS_BLOCK}
606
    @param dev_type: the type to look for
607
    @rtype: boolean
608
    @return: boolean indicating if a device of the given type was found or not
609

610
    """
611
    if self.children:
612
      for child in self.children:
613
        if child.IsBasedOnDiskType(dev_type):
614
          return True
615
    return self.dev_type == dev_type
616

    
617
  def GetNodes(self, node):
618
    """This function returns the nodes this device lives on.
619

620
    Given the node on which the parent of the device lives on (or, in
621
    case of a top-level device, the primary node of the devices'
622
    instance), this function will return a list of nodes on which this
623
    devices needs to (or can) be assembled.
624

625
    """
626
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
627
                         constants.LD_BLOCKDEV]:
628
      result = [node]
629
    elif self.dev_type in constants.LDS_DRBD:
630
      result = [self.logical_id[0], self.logical_id[1]]
631
      if node not in result:
632
        raise errors.ConfigurationError("DRBD device passed unknown node")
633
    else:
634
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
635
    return result
636

    
637
  def ComputeNodeTree(self, parent_node):
638
    """Compute the node/disk tree for this disk and its children.
639

640
    This method, given the node on which the parent disk lives, will
641
    return the list of all (node, disk) pairs which describe the disk
642
    tree in the most compact way. For example, a drbd/lvm stack
643
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
644
    which represents all the top-level devices on the nodes.
645

646
    """
647
    my_nodes = self.GetNodes(parent_node)
648
    result = [(node, self) for node in my_nodes]
649
    if not self.children:
650
      # leaf device
651
      return result
652
    for node in my_nodes:
653
      for child in self.children:
654
        child_result = child.ComputeNodeTree(node)
655
        if len(child_result) == 1:
656
          # child (and all its descendants) is simple, doesn't split
657
          # over multiple hosts, so we don't need to describe it, our
658
          # own entry for this node describes it completely
659
          continue
660
        else:
661
          # check if child nodes differ from my nodes; note that
662
          # subdisk can differ from the child itself, and be instead
663
          # one of its descendants
664
          for subnode, subdisk in child_result:
665
            if subnode not in my_nodes:
666
              result.append((subnode, subdisk))
667
            # otherwise child is under our own node, so we ignore this
668
            # entry (but probably the other results in the list will
669
            # be different)
670
    return result
671

    
672
  def ComputeGrowth(self, amount):
673
    """Compute the per-VG growth requirements.
674

675
    This only works for VG-based disks.
676

677
    @type amount: integer
678
    @param amount: the desired increase in (user-visible) disk space
679
    @rtype: dict
680
    @return: a dictionary of volume-groups and the required size
681

682
    """
683
    if self.dev_type == constants.LD_LV:
684
      return {self.logical_id[0]: amount}
685
    elif self.dev_type == constants.LD_DRBD8:
686
      if self.children:
687
        return self.children[0].ComputeGrowth(amount)
688
      else:
689
        return {}
690
    else:
691
      # Other disk types do not require VG space
692
      return {}
693

    
694
  def RecordGrow(self, amount):
695
    """Update the size of this disk after growth.
696

697
    This method recurses over the disks's children and updates their
698
    size correspondigly. The method needs to be kept in sync with the
699
    actual algorithms from bdev.
700

701
    """
702
    if self.dev_type in (constants.LD_LV, constants.LD_FILE):
703
      self.size += amount
704
    elif self.dev_type == constants.LD_DRBD8:
705
      if self.children:
706
        self.children[0].RecordGrow(amount)
707
      self.size += amount
708
    else:
709
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
710
                                   " disk type %s" % self.dev_type)
711

    
712
  def UnsetSize(self):
713
    """Sets recursively the size to zero for the disk and its children.
714

715
    """
716
    if self.children:
717
      for child in self.children:
718
        child.UnsetSize()
719
    self.size = 0
720

    
721
  def SetPhysicalID(self, target_node, nodes_ip):
722
    """Convert the logical ID to the physical ID.
723

724
    This is used only for drbd, which needs ip/port configuration.
725

726
    The routine descends down and updates its children also, because
727
    this helps when the only the top device is passed to the remote
728
    node.
729

730
    Arguments:
731
      - target_node: the node we wish to configure for
732
      - nodes_ip: a mapping of node name to ip
733

734
    The target_node must exist in in nodes_ip, and must be one of the
735
    nodes in the logical ID for each of the DRBD devices encountered
736
    in the disk tree.
737

738
    """
739
    if self.children:
740
      for child in self.children:
741
        child.SetPhysicalID(target_node, nodes_ip)
742

    
743
    if self.logical_id is None and self.physical_id is not None:
744
      return
745
    if self.dev_type in constants.LDS_DRBD:
746
      pnode, snode, port, pminor, sminor, secret = self.logical_id
747
      if target_node not in (pnode, snode):
748
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
749
                                        target_node)
750
      pnode_ip = nodes_ip.get(pnode, None)
751
      snode_ip = nodes_ip.get(snode, None)
752
      if pnode_ip is None or snode_ip is None:
753
        raise errors.ConfigurationError("Can't find primary or secondary node"
754
                                        " for %s" % str(self))
755
      p_data = (pnode_ip, port)
756
      s_data = (snode_ip, port)
757
      if pnode == target_node:
758
        self.physical_id = p_data + s_data + (pminor, secret)
759
      else: # it must be secondary, we tested above
760
        self.physical_id = s_data + p_data + (sminor, secret)
761
    else:
762
      self.physical_id = self.logical_id
763
    return
764

    
765
  def ToDict(self):
766
    """Disk-specific conversion to standard python types.
767

768
    This replaces the children lists of objects with lists of
769
    standard python types.
770

771
    """
772
    bo = super(Disk, self).ToDict()
773

    
774
    for attr in ("children",):
775
      alist = bo.get(attr, None)
776
      if alist:
777
        bo[attr] = self._ContainerToDicts(alist)
778
    return bo
779

    
780
  @classmethod
781
  def FromDict(cls, val):
782
    """Custom function for Disks
783

784
    """
785
    obj = super(Disk, cls).FromDict(val)
786
    if obj.children:
787
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
788
    if obj.logical_id and isinstance(obj.logical_id, list):
789
      obj.logical_id = tuple(obj.logical_id)
790
    if obj.physical_id and isinstance(obj.physical_id, list):
791
      obj.physical_id = tuple(obj.physical_id)
792
    if obj.dev_type in constants.LDS_DRBD:
793
      # we need a tuple of length six here
794
      if len(obj.logical_id) < 6:
795
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
796
    return obj
797

    
798
  def __str__(self):
799
    """Custom str() formatter for disks.
800

801
    """
802
    if self.dev_type == constants.LD_LV:
803
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
804
    elif self.dev_type in constants.LDS_DRBD:
805
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
806
      val = "<DRBD8("
807
      if self.physical_id is None:
808
        phy = "unconfigured"
809
      else:
810
        phy = ("configured as %s:%s %s:%s" %
811
               (self.physical_id[0], self.physical_id[1],
812
                self.physical_id[2], self.physical_id[3]))
813

    
814
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
815
              (node_a, minor_a, node_b, minor_b, port, phy))
816
      if self.children and self.children.count(None) == 0:
817
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
818
      else:
819
        val += "no local storage"
820
    else:
821
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
822
             (self.dev_type, self.logical_id, self.physical_id, self.children))
823
    if self.iv_name is None:
824
      val += ", not visible"
825
    else:
826
      val += ", visible as /dev/%s" % self.iv_name
827
    if isinstance(self.size, int):
828
      val += ", size=%dm)>" % self.size
829
    else:
830
      val += ", size='%s')>" % (self.size,)
831
    return val
832

    
833
  def Verify(self):
834
    """Checks that this disk is correctly configured.
835

836
    """
837
    all_errors = []
838
    if self.mode not in constants.DISK_ACCESS_SET:
839
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
840
    return all_errors
841

    
842
  def UpgradeConfig(self):
843
    """Fill defaults for missing configuration values.
844

845
    """
846
    if self.children:
847
      for child in self.children:
848
        child.UpgradeConfig()
849

    
850
    if not self.params:
851
      self.params = constants.DISK_LD_DEFAULTS[self.dev_type].copy()
852
    else:
853
      self.params = FillDict(constants.DISK_LD_DEFAULTS[self.dev_type],
854
                             self.params)
855
    # add here config upgrade for this disk
856

    
857

    
858
class InstancePolicy(ConfigObject):
859
  """Config object representing instance policy limits dictionary."""
860
  __slots__ = ["min", "max", "std"]
861

    
862
  @classmethod
863
  def CheckParameterSyntax(cls, ipolicy):
864
    """ Check the instance policy for validity.
865

866
    """
867
    for param in constants.ISPECS_PARAMETERS:
868
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
869

    
870
  @classmethod
871
  def CheckISpecSyntax(cls, ipolicy, name):
872
    """Check the instance policy for validity on a given key.
873

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

877
    @type ipolicy: dict
878
    @param ipolicy: dictionary with min, max, std specs
879
    @type name: string
880
    @param name: what are the limits for
881
    @raise errors.ConfigureError: when specs for given name are not valid
882

883
    """
884
    min_v = ipolicy[constants.MIN_ISPECS].get(name, 0)
885
    std_v = ipolicy[constants.STD_ISPECS].get(name, min_v)
886
    max_v = ipolicy[constants.MAX_ISPECS].get(name, std_v)
887
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
888
           (name,
889
            ipolicy[constants.MIN_ISPECS].get(name, "-"),
890
            ipolicy[constants.MAX_ISPECS].get(name, "-"),
891
            ipolicy[constants.STD_ISPECS].get(name, "-")))
892
    if min_v > std_v or std_v > max_v:
893
      raise errors.ConfigurationError(err)
894

    
895

    
896
class Instance(TaggableObject):
897
  """Config object representing an instance."""
898
  __slots__ = [
899
    "name",
900
    "primary_node",
901
    "os",
902
    "hypervisor",
903
    "hvparams",
904
    "beparams",
905
    "osparams",
906
    "admin_state",
907
    "nics",
908
    "disks",
909
    "disk_template",
910
    "network_port",
911
    "serial_no",
912
    ] + _TIMESTAMPS + _UUID
913

    
914
  def _ComputeSecondaryNodes(self):
915
    """Compute the list of secondary nodes.
916

917
    This is a simple wrapper over _ComputeAllNodes.
918

919
    """
920
    all_nodes = set(self._ComputeAllNodes())
921
    all_nodes.discard(self.primary_node)
922
    return tuple(all_nodes)
923

    
924
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
925
                             "List of secondary nodes")
926

    
927
  def _ComputeAllNodes(self):
928
    """Compute the list of all nodes.
929

930
    Since the data is already there (in the drbd disks), keeping it as
931
    a separate normal attribute is redundant and if not properly
932
    synchronised can cause problems. Thus it's better to compute it
933
    dynamically.
934

935
    """
936
    def _Helper(nodes, device):
937
      """Recursively computes nodes given a top device."""
938
      if device.dev_type in constants.LDS_DRBD:
939
        nodea, nodeb = device.logical_id[:2]
940
        nodes.add(nodea)
941
        nodes.add(nodeb)
942
      if device.children:
943
        for child in device.children:
944
          _Helper(nodes, child)
945

    
946
    all_nodes = set()
947
    all_nodes.add(self.primary_node)
948
    for device in self.disks:
949
      _Helper(all_nodes, device)
950
    return tuple(all_nodes)
951

    
952
  all_nodes = property(_ComputeAllNodes, None, None,
953
                       "List of all nodes of the instance")
954

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

958
    This function figures out what logical volumes should belong on
959
    which nodes, recursing through a device tree.
960

961
    @param lvmap: optional dictionary to receive the
962
        'node' : ['lv', ...] data.
963

964
    @return: None if lvmap arg is given, otherwise, a dictionary of
965
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
966
        volumeN is of the form "vg_name/lv_name", compatible with
967
        GetVolumeList()
968

969
    """
970
    if node == None:
971
      node = self.primary_node
972

    
973
    if lvmap is None:
974
      lvmap = {
975
        node: [],
976
        }
977
      ret = lvmap
978
    else:
979
      if not node in lvmap:
980
        lvmap[node] = []
981
      ret = None
982

    
983
    if not devs:
984
      devs = self.disks
985

    
986
    for dev in devs:
987
      if dev.dev_type == constants.LD_LV:
988
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
989

    
990
      elif dev.dev_type in constants.LDS_DRBD:
991
        if dev.children:
992
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
993
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
994

    
995
      elif dev.children:
996
        self.MapLVsByNode(lvmap, dev.children, node)
997

    
998
    return ret
999

    
1000
  def FindDisk(self, idx):
1001
    """Find a disk given having a specified index.
1002

1003
    This is just a wrapper that does validation of the index.
1004

1005
    @type idx: int
1006
    @param idx: the disk index
1007
    @rtype: L{Disk}
1008
    @return: the corresponding disk
1009
    @raise errors.OpPrereqError: when the given index is not valid
1010

1011
    """
1012
    try:
1013
      idx = int(idx)
1014
      return self.disks[idx]
1015
    except (TypeError, ValueError), err:
1016
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1017
                                 errors.ECODE_INVAL)
1018
    except IndexError:
1019
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1020
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1021
                                 errors.ECODE_INVAL)
1022

    
1023
  def ToDict(self):
1024
    """Instance-specific conversion to standard python types.
1025

1026
    This replaces the children lists of objects with lists of standard
1027
    python types.
1028

1029
    """
1030
    bo = super(Instance, self).ToDict()
1031

    
1032
    for attr in "nics", "disks":
1033
      alist = bo.get(attr, None)
1034
      if alist:
1035
        nlist = self._ContainerToDicts(alist)
1036
      else:
1037
        nlist = []
1038
      bo[attr] = nlist
1039
    return bo
1040

    
1041
  @classmethod
1042
  def FromDict(cls, val):
1043
    """Custom function for instances.
1044

1045
    """
1046
    if "admin_state" not in val:
1047
      if val.get("admin_up", False):
1048
        val["admin_state"] = constants.ADMINST_UP
1049
      else:
1050
        val["admin_state"] = constants.ADMINST_DOWN
1051
    if "admin_up" in val:
1052
      del val["admin_up"]
1053
    obj = super(Instance, cls).FromDict(val)
1054
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1055
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1056
    return obj
1057

    
1058
  def UpgradeConfig(self):
1059
    """Fill defaults for missing configuration values.
1060

1061
    """
1062
    for nic in self.nics:
1063
      nic.UpgradeConfig()
1064
    for disk in self.disks:
1065
      disk.UpgradeConfig()
1066
    if self.hvparams:
1067
      for key in constants.HVC_GLOBALS:
1068
        try:
1069
          del self.hvparams[key]
1070
        except KeyError:
1071
          pass
1072
    if self.osparams is None:
1073
      self.osparams = {}
1074
    UpgradeBeParams(self.beparams)
1075

    
1076

    
1077
class OS(ConfigObject):
1078
  """Config object representing an operating system.
1079

1080
  @type supported_parameters: list
1081
  @ivar supported_parameters: a list of tuples, name and description,
1082
      containing the supported parameters by this OS
1083

1084
  @type VARIANT_DELIM: string
1085
  @cvar VARIANT_DELIM: the variant delimiter
1086

1087
  """
1088
  __slots__ = [
1089
    "name",
1090
    "path",
1091
    "api_versions",
1092
    "create_script",
1093
    "export_script",
1094
    "import_script",
1095
    "rename_script",
1096
    "verify_script",
1097
    "supported_variants",
1098
    "supported_parameters",
1099
    ]
1100

    
1101
  VARIANT_DELIM = "+"
1102

    
1103
  @classmethod
1104
  def SplitNameVariant(cls, name):
1105
    """Splits the name into the proper name and variant.
1106

1107
    @param name: the OS (unprocessed) name
1108
    @rtype: list
1109
    @return: a list of two elements; if the original name didn't
1110
        contain a variant, it's returned as an empty string
1111

1112
    """
1113
    nv = name.split(cls.VARIANT_DELIM, 1)
1114
    if len(nv) == 1:
1115
      nv.append("")
1116
    return nv
1117

    
1118
  @classmethod
1119
  def GetName(cls, name):
1120
    """Returns the proper name of the os (without the variant).
1121

1122
    @param name: the OS (unprocessed) name
1123

1124
    """
1125
    return cls.SplitNameVariant(name)[0]
1126

    
1127
  @classmethod
1128
  def GetVariant(cls, name):
1129
    """Returns the variant the os (without the base name).
1130

1131
    @param name: the OS (unprocessed) name
1132

1133
    """
1134
    return cls.SplitNameVariant(name)[1]
1135

    
1136

    
1137
class NodeHvState(ConfigObject):
1138
  """Hypvervisor state on a node.
1139

1140
  @ivar mem_total: Total amount of memory
1141
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1142
    available)
1143
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1144
    rounding
1145
  @ivar mem_inst: Memory used by instances living on node
1146
  @ivar cpu_total: Total node CPU core count
1147
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1148

1149
  """
1150
  __slots__ = [
1151
    "mem_total",
1152
    "mem_node",
1153
    "mem_hv",
1154
    "mem_inst",
1155
    "cpu_total",
1156
    "cpu_node",
1157
    ] + _TIMESTAMPS
1158

    
1159

    
1160
class NodeDiskState(ConfigObject):
1161
  """Disk state on a node.
1162

1163
  """
1164
  __slots__ = [
1165
    "total",
1166
    "reserved",
1167
    "overhead",
1168
    ] + _TIMESTAMPS
1169

    
1170

    
1171
class Node(TaggableObject):
1172
  """Config object representing a node.
1173

1174
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1175
  @ivar hv_state_static: Hypervisor state overriden by user
1176
  @ivar disk_state: Disk state (e.g. free space)
1177
  @ivar disk_state_static: Disk state overriden by user
1178

1179
  """
1180
  __slots__ = [
1181
    "name",
1182
    "primary_ip",
1183
    "secondary_ip",
1184
    "serial_no",
1185
    "master_candidate",
1186
    "offline",
1187
    "drained",
1188
    "group",
1189
    "master_capable",
1190
    "vm_capable",
1191
    "ndparams",
1192
    "powered",
1193
    "hv_state",
1194
    "hv_state_static",
1195
    "disk_state",
1196
    "disk_state_static",
1197
    ] + _TIMESTAMPS + _UUID
1198

    
1199
  def UpgradeConfig(self):
1200
    """Fill defaults for missing configuration values.
1201

1202
    """
1203
    # pylint: disable=E0203
1204
    # because these are "defined" via slots, not manually
1205
    if self.master_capable is None:
1206
      self.master_capable = True
1207

    
1208
    if self.vm_capable is None:
1209
      self.vm_capable = True
1210

    
1211
    if self.ndparams is None:
1212
      self.ndparams = {}
1213

    
1214
    if self.powered is None:
1215
      self.powered = True
1216

    
1217
  def ToDict(self):
1218
    """Custom function for serializing.
1219

1220
    """
1221
    data = super(Node, self).ToDict()
1222

    
1223
    hv_state = data.get("hv_state", None)
1224
    if hv_state is not None:
1225
      data["hv_state"] = self._ContainerToDicts(hv_state)
1226

    
1227
    disk_state = data.get("disk_state", None)
1228
    if disk_state is not None:
1229
      data["disk_state"] = \
1230
        dict((key, self._ContainerToDicts(value))
1231
             for (key, value) in disk_state.items())
1232

    
1233
    return data
1234

    
1235
  @classmethod
1236
  def FromDict(cls, val):
1237
    """Custom function for deserializing.
1238

1239
    """
1240
    obj = super(Node, cls).FromDict(val)
1241

    
1242
    if obj.hv_state is not None:
1243
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1244

    
1245
    if obj.disk_state is not None:
1246
      obj.disk_state = \
1247
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1248
             for (key, value) in obj.disk_state.items())
1249

    
1250
    return obj
1251

    
1252

    
1253
class NodeGroup(TaggableObject):
1254
  """Config object representing a node group."""
1255
  __slots__ = [
1256
    "name",
1257
    "members",
1258
    "ndparams",
1259
    "diskparams",
1260
    "ipolicy",
1261
    "serial_no",
1262
    "hv_state_static",
1263
    "disk_state_static",
1264
    "alloc_policy",
1265
    ] + _TIMESTAMPS + _UUID
1266

    
1267
  def ToDict(self):
1268
    """Custom function for nodegroup.
1269

1270
    This discards the members object, which gets recalculated and is only kept
1271
    in memory.
1272

1273
    """
1274
    mydict = super(NodeGroup, self).ToDict()
1275
    del mydict["members"]
1276
    return mydict
1277

    
1278
  @classmethod
1279
  def FromDict(cls, val):
1280
    """Custom function for nodegroup.
1281

1282
    The members slot is initialized to an empty list, upon deserialization.
1283

1284
    """
1285
    obj = super(NodeGroup, cls).FromDict(val)
1286
    obj.members = []
1287
    return obj
1288

    
1289
  def UpgradeConfig(self):
1290
    """Fill defaults for missing configuration values.
1291

1292
    """
1293
    if self.ndparams is None:
1294
      self.ndparams = {}
1295

    
1296
    if self.serial_no is None:
1297
      self.serial_no = 1
1298

    
1299
    if self.alloc_policy is None:
1300
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1301

    
1302
    # We only update mtime, and not ctime, since we would not be able to provide
1303
    # a correct value for creation time.
1304
    if self.mtime is None:
1305
      self.mtime = time.time()
1306

    
1307
    self.diskparams = UpgradeDiskParams(self.diskparams)
1308
    if self.ipolicy is None:
1309
      self.ipolicy = MakeEmptyIPolicy()
1310

    
1311
  def FillND(self, node):
1312
    """Return filled out ndparams for L{objects.Node}
1313

1314
    @type node: L{objects.Node}
1315
    @param node: A Node object to fill
1316
    @return a copy of the node's ndparams with defaults filled
1317

1318
    """
1319
    return self.SimpleFillND(node.ndparams)
1320

    
1321
  def SimpleFillND(self, ndparams):
1322
    """Fill a given ndparams dict with defaults.
1323

1324
    @type ndparams: dict
1325
    @param ndparams: the dict to fill
1326
    @rtype: dict
1327
    @return: a copy of the passed in ndparams with missing keys filled
1328
        from the node group defaults
1329

1330
    """
1331
    return FillDict(self.ndparams, ndparams)
1332

    
1333

    
1334
class Cluster(TaggableObject):
1335
  """Config object representing the cluster."""
1336
  __slots__ = [
1337
    "serial_no",
1338
    "rsahostkeypub",
1339
    "highest_used_port",
1340
    "tcpudp_port_pool",
1341
    "mac_prefix",
1342
    "volume_group_name",
1343
    "reserved_lvs",
1344
    "drbd_usermode_helper",
1345
    "default_bridge",
1346
    "default_hypervisor",
1347
    "master_node",
1348
    "master_ip",
1349
    "master_netdev",
1350
    "master_netmask",
1351
    "use_external_mip_script",
1352
    "cluster_name",
1353
    "file_storage_dir",
1354
    "shared_file_storage_dir",
1355
    "enabled_hypervisors",
1356
    "hvparams",
1357
    "ipolicy",
1358
    "os_hvp",
1359
    "beparams",
1360
    "osparams",
1361
    "nicparams",
1362
    "ndparams",
1363
    "diskparams",
1364
    "candidate_pool_size",
1365
    "modify_etc_hosts",
1366
    "modify_ssh_setup",
1367
    "maintain_node_health",
1368
    "uid_pool",
1369
    "default_iallocator",
1370
    "hidden_os",
1371
    "blacklisted_os",
1372
    "primary_ip_family",
1373
    "prealloc_wipe_disks",
1374
    "hv_state_static",
1375
    "disk_state_static",
1376
    ] + _TIMESTAMPS + _UUID
1377

    
1378
  def UpgradeConfig(self):
1379
    """Fill defaults for missing configuration values.
1380

1381
    """
1382
    # pylint: disable=E0203
1383
    # because these are "defined" via slots, not manually
1384
    if self.hvparams is None:
1385
      self.hvparams = constants.HVC_DEFAULTS
1386
    else:
1387
      for hypervisor in self.hvparams:
1388
        self.hvparams[hypervisor] = FillDict(
1389
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1390

    
1391
    if self.os_hvp is None:
1392
      self.os_hvp = {}
1393

    
1394
    # osparams added before 2.2
1395
    if self.osparams is None:
1396
      self.osparams = {}
1397

    
1398
    if self.ndparams is None:
1399
      self.ndparams = constants.NDC_DEFAULTS
1400

    
1401
    self.beparams = UpgradeGroupedParams(self.beparams,
1402
                                         constants.BEC_DEFAULTS)
1403
    for beparams_group in self.beparams:
1404
      UpgradeBeParams(self.beparams[beparams_group])
1405

    
1406
    migrate_default_bridge = not self.nicparams
1407
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1408
                                          constants.NICC_DEFAULTS)
1409
    if migrate_default_bridge:
1410
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1411
        self.default_bridge
1412

    
1413
    if self.modify_etc_hosts is None:
1414
      self.modify_etc_hosts = True
1415

    
1416
    if self.modify_ssh_setup is None:
1417
      self.modify_ssh_setup = True
1418

    
1419
    # default_bridge is no longer used in 2.1. The slot is left there to
1420
    # support auto-upgrading. It can be removed once we decide to deprecate
1421
    # upgrading straight from 2.0.
1422
    if self.default_bridge is not None:
1423
      self.default_bridge = None
1424

    
1425
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1426
    # code can be removed once upgrading straight from 2.0 is deprecated.
1427
    if self.default_hypervisor is not None:
1428
      self.enabled_hypervisors = ([self.default_hypervisor] +
1429
        [hvname for hvname in self.enabled_hypervisors
1430
         if hvname != self.default_hypervisor])
1431
      self.default_hypervisor = None
1432

    
1433
    # maintain_node_health added after 2.1.1
1434
    if self.maintain_node_health is None:
1435
      self.maintain_node_health = False
1436

    
1437
    if self.uid_pool is None:
1438
      self.uid_pool = []
1439

    
1440
    if self.default_iallocator is None:
1441
      self.default_iallocator = ""
1442

    
1443
    # reserved_lvs added before 2.2
1444
    if self.reserved_lvs is None:
1445
      self.reserved_lvs = []
1446

    
1447
    # hidden and blacklisted operating systems added before 2.2.1
1448
    if self.hidden_os is None:
1449
      self.hidden_os = []
1450

    
1451
    if self.blacklisted_os is None:
1452
      self.blacklisted_os = []
1453

    
1454
    # primary_ip_family added before 2.3
1455
    if self.primary_ip_family is None:
1456
      self.primary_ip_family = AF_INET
1457

    
1458
    if self.master_netmask is None:
1459
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1460
      self.master_netmask = ipcls.iplen
1461

    
1462
    if self.prealloc_wipe_disks is None:
1463
      self.prealloc_wipe_disks = False
1464

    
1465
    # shared_file_storage_dir added before 2.5
1466
    if self.shared_file_storage_dir is None:
1467
      self.shared_file_storage_dir = ""
1468

    
1469
    if self.use_external_mip_script is None:
1470
      self.use_external_mip_script = False
1471

    
1472
    self.diskparams = UpgradeDiskParams(self.diskparams)
1473

    
1474
    # instance policy added before 2.6
1475
    if self.ipolicy is None:
1476
      self.ipolicy = MakeEmptyIPolicy()
1477

    
1478
  @property
1479
  def primary_hypervisor(self):
1480
    """The first hypervisor is the primary.
1481

1482
    Useful, for example, for L{Node}'s hv/disk state.
1483

1484
    """
1485
    return self.enabled_hypervisors[0]
1486

    
1487
  def ToDict(self):
1488
    """Custom function for cluster.
1489

1490
    """
1491
    mydict = super(Cluster, self).ToDict()
1492
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1493
    return mydict
1494

    
1495
  @classmethod
1496
  def FromDict(cls, val):
1497
    """Custom function for cluster.
1498

1499
    """
1500
    obj = super(Cluster, cls).FromDict(val)
1501
    if not isinstance(obj.tcpudp_port_pool, set):
1502
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1503
    return obj
1504

    
1505
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1506
    """Get the default hypervisor parameters for the cluster.
1507

1508
    @param hypervisor: the hypervisor name
1509
    @param os_name: if specified, we'll also update the defaults for this OS
1510
    @param skip_keys: if passed, list of keys not to use
1511
    @return: the defaults dict
1512

1513
    """
1514
    if skip_keys is None:
1515
      skip_keys = []
1516

    
1517
    fill_stack = [self.hvparams.get(hypervisor, {})]
1518
    if os_name is not None:
1519
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1520
      fill_stack.append(os_hvp)
1521

    
1522
    ret_dict = {}
1523
    for o_dict in fill_stack:
1524
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1525

    
1526
    return ret_dict
1527

    
1528
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1529
    """Fill a given hvparams dict with cluster defaults.
1530

1531
    @type hv_name: string
1532
    @param hv_name: the hypervisor to use
1533
    @type os_name: string
1534
    @param os_name: the OS to use for overriding the hypervisor defaults
1535
    @type skip_globals: boolean
1536
    @param skip_globals: if True, the global hypervisor parameters will
1537
        not be filled
1538
    @rtype: dict
1539
    @return: a copy of the given hvparams with missing keys filled from
1540
        the cluster defaults
1541

1542
    """
1543
    if skip_globals:
1544
      skip_keys = constants.HVC_GLOBALS
1545
    else:
1546
      skip_keys = []
1547

    
1548
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1549
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1550

    
1551
  def FillHV(self, instance, skip_globals=False):
1552
    """Fill an instance's hvparams dict with cluster defaults.
1553

1554
    @type instance: L{objects.Instance}
1555
    @param instance: the instance parameter to fill
1556
    @type skip_globals: boolean
1557
    @param skip_globals: if True, the global hypervisor parameters will
1558
        not be filled
1559
    @rtype: dict
1560
    @return: a copy of the instance's hvparams with missing keys filled from
1561
        the cluster defaults
1562

1563
    """
1564
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1565
                             instance.hvparams, skip_globals)
1566

    
1567
  def SimpleFillBE(self, beparams):
1568
    """Fill a given beparams dict with cluster defaults.
1569

1570
    @type beparams: dict
1571
    @param beparams: the dict to fill
1572
    @rtype: dict
1573
    @return: a copy of the passed in beparams with missing keys filled
1574
        from the cluster defaults
1575

1576
    """
1577
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1578

    
1579
  def FillBE(self, instance):
1580
    """Fill an instance's beparams dict with cluster defaults.
1581

1582
    @type instance: L{objects.Instance}
1583
    @param instance: the instance parameter to fill
1584
    @rtype: dict
1585
    @return: a copy of the instance's beparams with missing keys filled from
1586
        the cluster defaults
1587

1588
    """
1589
    return self.SimpleFillBE(instance.beparams)
1590

    
1591
  def SimpleFillNIC(self, nicparams):
1592
    """Fill a given nicparams dict with cluster defaults.
1593

1594
    @type nicparams: dict
1595
    @param nicparams: the dict to fill
1596
    @rtype: dict
1597
    @return: a copy of the passed in nicparams with missing keys filled
1598
        from the cluster defaults
1599

1600
    """
1601
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1602

    
1603
  def SimpleFillOS(self, os_name, os_params):
1604
    """Fill an instance's osparams dict with cluster defaults.
1605

1606
    @type os_name: string
1607
    @param os_name: the OS name to use
1608
    @type os_params: dict
1609
    @param os_params: the dict to fill with default values
1610
    @rtype: dict
1611
    @return: a copy of the instance's osparams with missing keys filled from
1612
        the cluster defaults
1613

1614
    """
1615
    name_only = os_name.split("+", 1)[0]
1616
    # base OS
1617
    result = self.osparams.get(name_only, {})
1618
    # OS with variant
1619
    result = FillDict(result, self.osparams.get(os_name, {}))
1620
    # specified params
1621
    return FillDict(result, os_params)
1622

    
1623
  @staticmethod
1624
  def SimpleFillHvState(hv_state):
1625
    """Fill an hv_state sub dict with cluster defaults.
1626

1627
    """
1628
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1629

    
1630
  @staticmethod
1631
  def SimpleFillDiskState(disk_state):
1632
    """Fill an disk_state sub dict with cluster defaults.
1633

1634
    """
1635
    return FillDict(constants.DS_DEFAULTS, disk_state)
1636

    
1637
  def FillND(self, node, nodegroup):
1638
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1639

1640
    @type node: L{objects.Node}
1641
    @param node: A Node object to fill
1642
    @type nodegroup: L{objects.NodeGroup}
1643
    @param nodegroup: A Node object to fill
1644
    @return a copy of the node's ndparams with defaults filled
1645

1646
    """
1647
    return self.SimpleFillND(nodegroup.FillND(node))
1648

    
1649
  def SimpleFillND(self, ndparams):
1650
    """Fill a given ndparams dict with defaults.
1651

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

1658
    """
1659
    return FillDict(self.ndparams, ndparams)
1660

    
1661
  def SimpleFillIPolicy(self, ipolicy):
1662
    """ Fill instance policy dict with defaults.
1663

1664
    @type ipolicy: dict
1665
    @param ipolicy: the dict to fill
1666
    @rtype: dict
1667
    @return: a copy of passed ipolicy with missing keys filled from
1668
      the cluster defaults
1669

1670
    """
1671
    return FillDictOfDicts(self.ipolicy, ipolicy)
1672

    
1673

    
1674
class BlockDevStatus(ConfigObject):
1675
  """Config object representing the status of a block device."""
1676
  __slots__ = [
1677
    "dev_path",
1678
    "major",
1679
    "minor",
1680
    "sync_percent",
1681
    "estimated_time",
1682
    "is_degraded",
1683
    "ldisk_status",
1684
    ]
1685

    
1686

    
1687
class ImportExportStatus(ConfigObject):
1688
  """Config object representing the status of an import or export."""
1689
  __slots__ = [
1690
    "recent_output",
1691
    "listen_port",
1692
    "connected",
1693
    "progress_mbytes",
1694
    "progress_throughput",
1695
    "progress_eta",
1696
    "progress_percent",
1697
    "exit_status",
1698
    "error_message",
1699
    ] + _TIMESTAMPS
1700

    
1701

    
1702
class ImportExportOptions(ConfigObject):
1703
  """Options for import/export daemon
1704

1705
  @ivar key_name: X509 key name (None for cluster certificate)
1706
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1707
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1708
  @ivar magic: Used to ensure the connection goes to the right disk
1709
  @ivar ipv6: Whether to use IPv6
1710
  @ivar connect_timeout: Number of seconds for establishing connection
1711

1712
  """
1713
  __slots__ = [
1714
    "key_name",
1715
    "ca_pem",
1716
    "compress",
1717
    "magic",
1718
    "ipv6",
1719
    "connect_timeout",
1720
    ]
1721

    
1722

    
1723
class ConfdRequest(ConfigObject):
1724
  """Object holding a confd request.
1725

1726
  @ivar protocol: confd protocol version
1727
  @ivar type: confd query type
1728
  @ivar query: query request
1729
  @ivar rsalt: requested reply salt
1730

1731
  """
1732
  __slots__ = [
1733
    "protocol",
1734
    "type",
1735
    "query",
1736
    "rsalt",
1737
    ]
1738

    
1739

    
1740
class ConfdReply(ConfigObject):
1741
  """Object holding a confd reply.
1742

1743
  @ivar protocol: confd protocol version
1744
  @ivar status: reply status code (ok, error)
1745
  @ivar answer: confd query reply
1746
  @ivar serial: configuration serial number
1747

1748
  """
1749
  __slots__ = [
1750
    "protocol",
1751
    "status",
1752
    "answer",
1753
    "serial",
1754
    ]
1755

    
1756

    
1757
class QueryFieldDefinition(ConfigObject):
1758
  """Object holding a query field definition.
1759

1760
  @ivar name: Field name
1761
  @ivar title: Human-readable title
1762
  @ivar kind: Field type
1763
  @ivar doc: Human-readable description
1764

1765
  """
1766
  __slots__ = [
1767
    "name",
1768
    "title",
1769
    "kind",
1770
    "doc",
1771
    ]
1772

    
1773

    
1774
class _QueryResponseBase(ConfigObject):
1775
  __slots__ = [
1776
    "fields",
1777
    ]
1778

    
1779
  def ToDict(self):
1780
    """Custom function for serializing.
1781

1782
    """
1783
    mydict = super(_QueryResponseBase, self).ToDict()
1784
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1785
    return mydict
1786

    
1787
  @classmethod
1788
  def FromDict(cls, val):
1789
    """Custom function for de-serializing.
1790

1791
    """
1792
    obj = super(_QueryResponseBase, cls).FromDict(val)
1793
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1794
    return obj
1795

    
1796

    
1797
class QueryRequest(ConfigObject):
1798
  """Object holding a query request.
1799

1800
  """
1801
  __slots__ = [
1802
    "what",
1803
    "fields",
1804
    "qfilter",
1805
    ]
1806

    
1807

    
1808
class QueryResponse(_QueryResponseBase):
1809
  """Object holding the response to a query.
1810

1811
  @ivar fields: List of L{QueryFieldDefinition} objects
1812
  @ivar data: Requested data
1813

1814
  """
1815
  __slots__ = [
1816
    "data",
1817
    ]
1818

    
1819

    
1820
class QueryFieldsRequest(ConfigObject):
1821
  """Object holding a request for querying available fields.
1822

1823
  """
1824
  __slots__ = [
1825
    "what",
1826
    "fields",
1827
    ]
1828

    
1829

    
1830
class QueryFieldsResponse(_QueryResponseBase):
1831
  """Object holding the response to a query for fields.
1832

1833
  @ivar fields: List of L{QueryFieldDefinition} objects
1834

1835
  """
1836
  __slots__ = [
1837
    ]
1838

    
1839

    
1840
class MigrationStatus(ConfigObject):
1841
  """Object holding the status of a migration.
1842

1843
  """
1844
  __slots__ = [
1845
    "status",
1846
    "transferred_ram",
1847
    "total_ram",
1848
    ]
1849

    
1850

    
1851
class InstanceConsole(ConfigObject):
1852
  """Object describing how to access the console of an instance.
1853

1854
  """
1855
  __slots__ = [
1856
    "instance",
1857
    "kind",
1858
    "message",
1859
    "host",
1860
    "port",
1861
    "user",
1862
    "command",
1863
    "display",
1864
    ]
1865

    
1866
  def Validate(self):
1867
    """Validates contents of this object.
1868

1869
    """
1870
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1871
    assert self.instance, "Missing instance name"
1872
    assert self.message or self.kind in [constants.CONS_SSH,
1873
                                         constants.CONS_SPICE,
1874
                                         constants.CONS_VNC]
1875
    assert self.host or self.kind == constants.CONS_MESSAGE
1876
    assert self.port or self.kind in [constants.CONS_MESSAGE,
1877
                                      constants.CONS_SSH]
1878
    assert self.user or self.kind in [constants.CONS_MESSAGE,
1879
                                      constants.CONS_SPICE,
1880
                                      constants.CONS_VNC]
1881
    assert self.command or self.kind in [constants.CONS_MESSAGE,
1882
                                         constants.CONS_SPICE,
1883
                                         constants.CONS_VNC]
1884
    assert self.display or self.kind in [constants.CONS_MESSAGE,
1885
                                         constants.CONS_SPICE,
1886
                                         constants.CONS_SSH]
1887
    return True
1888

    
1889

    
1890
class SerializableConfigParser(ConfigParser.SafeConfigParser):
1891
  """Simple wrapper over ConfigParse that allows serialization.
1892

1893
  This class is basically ConfigParser.SafeConfigParser with two
1894
  additional methods that allow it to serialize/unserialize to/from a
1895
  buffer.
1896

1897
  """
1898
  def Dumps(self):
1899
    """Dump this instance and return the string representation."""
1900
    buf = StringIO()
1901
    self.write(buf)
1902
    return buf.getvalue()
1903

    
1904
  @classmethod
1905
  def Loads(cls, data):
1906
    """Load data from a string."""
1907
    buf = StringIO(data)
1908
    cfp = cls()
1909
    cfp.readfp(buf)
1910
    return cfp