Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ dca1f84a

History | View | Annotate | Download (58.4 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", "Network"]
54

    
55
_TIMESTAMPS = ["ctime", "mtime"]
56
_UUID = ["uuid"]
57

    
58

    
59
def FillDict(defaults_dict, custom_dict, skip_keys=None):
60
  """Basic function to apply settings on top a default dict.
61

62
  @type defaults_dict: dict
63
  @param defaults_dict: dictionary holding the default values
64
  @type custom_dict: dict
65
  @param custom_dict: dictionary holding customized value
66
  @type skip_keys: list
67
  @param skip_keys: which keys not to fill
68
  @rtype: dict
69
  @return: dict with the 'full' values
70

71
  """
72
  ret_dict = copy.deepcopy(defaults_dict)
73
  ret_dict.update(custom_dict)
74
  if skip_keys:
75
    for k in skip_keys:
76
      try:
77
        del ret_dict[k]
78
      except KeyError:
79
        pass
80
  return ret_dict
81

    
82

    
83
def FillIPolicy(default_ipolicy, custom_ipolicy, skip_keys=None):
84
  """Fills an instance policy with defaults.
85

86
  """
87
  assert frozenset(default_ipolicy.keys()) == constants.IPOLICY_ALL_KEYS
88
  ret_dict = {}
89
  for key in constants.IPOLICY_ISPECS:
90
    ret_dict[key] = FillDict(default_ipolicy[key],
91
                             custom_ipolicy.get(key, {}),
92
                             skip_keys=skip_keys)
93
  # list items
94
  for key in [constants.IPOLICY_DTS]:
95
    ret_dict[key] = list(custom_ipolicy.get(key, default_ipolicy[key]))
96
  # other items which we know we can directly copy (immutables)
97
  for key in constants.IPOLICY_PARAMETERS:
98
    ret_dict[key] = custom_ipolicy.get(key, default_ipolicy[key])
99

    
100
  return ret_dict
101

    
102

    
103
def FillDiskParams(default_dparams, custom_dparams, skip_keys=None):
104
  """Fills the disk parameter defaults.
105

106
  @see: L{FillDict} for parameters and return value
107

108
  """
109
  assert frozenset(default_dparams.keys()) == constants.DISK_TEMPLATES
110

    
111
  return dict((dt, FillDict(default_dparams[dt], custom_dparams.get(dt, {}),
112
                             skip_keys=skip_keys))
113
              for dt in constants.DISK_TEMPLATES)
114

    
115

    
116
def UpgradeGroupedParams(target, defaults):
117
  """Update all groups for the target parameter.
118

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

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

    
132

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

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

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

    
146

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

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

155
  """
156
  if not diskparams:
157
    result = {}
158
  else:
159
    result = FillDiskParams(constants.DISK_DT_DEFAULTS, diskparams)
160

    
161
  return result
162

    
163

    
164
def UpgradeNDParams(ndparams):
165
  """Upgrade ndparams structure.
166

167
  @type ndparams: dict
168
  @param ndparams: disk parameters to upgrade
169
  @rtype: dict
170
  @return: the upgraded node parameters dict
171

172
  """
173
  if ndparams is None:
174
    ndparams = {}
175

    
176
  return FillDict(constants.NDC_DEFAULTS, ndparams)
177

    
178

    
179
def MakeEmptyIPolicy():
180
  """Create empty IPolicy dictionary.
181

182
  """
183
  return dict([
184
    (constants.ISPECS_MIN, {}),
185
    (constants.ISPECS_MAX, {}),
186
    (constants.ISPECS_STD, {}),
187
    ])
188

    
189

    
190
class ConfigObject(object):
191
  """A generic config object.
192

193
  It has the following properties:
194

195
    - provides somewhat safe recursive unpickling and pickling for its classes
196
    - unset attributes which are defined in slots are always returned
197
      as None instead of raising an error
198

199
  Classes derived from this must always declare __slots__ (we use many
200
  config objects and the memory reduction is useful)
201

202
  """
203
  __slots__ = []
204

    
205
  def __init__(self, **kwargs):
206
    for k, v in kwargs.iteritems():
207
      setattr(self, k, v)
208

    
209
  def __getattr__(self, name):
210
    if name not in self._all_slots():
211
      raise AttributeError("Invalid object attribute %s.%s" %
212
                           (type(self).__name__, name))
213
    return None
214

    
215
  def __setstate__(self, state):
216
    slots = self._all_slots()
217
    for name in state:
218
      if name in slots:
219
        setattr(self, name, state[name])
220

    
221
  @classmethod
222
  def _all_slots(cls):
223
    """Compute the list of all declared slots for a class.
224

225
    """
226
    slots = []
227
    for parent in cls.__mro__:
228
      slots.extend(getattr(parent, "__slots__", []))
229
    return slots
230

    
231
  #: Public getter for the defined slots
232
  GetAllSlots = _all_slots
233

    
234
  def ToDict(self):
235
    """Convert to a dict holding only standard python types.
236

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

243
    """
244
    result = {}
245
    for name in self._all_slots():
246
      value = getattr(self, name, None)
247
      if value is not None:
248
        result[name] = value
249
    return result
250

    
251
  __getstate__ = ToDict
252

    
253
  @classmethod
254
  def FromDict(cls, val):
255
    """Create an object from a dictionary.
256

257
    This generic routine takes a dict, instantiates a new instance of
258
    the given class, and sets attributes based on the dict content.
259

260
    As for `ToDict`, this does not work if the class has children
261
    who are ConfigObjects themselves (e.g. the nics list in an
262
    Instance), in which case the object should subclass the function
263
    and alter the objects.
264

265
    """
266
    if not isinstance(val, dict):
267
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
268
                                      " expected dict, got %s" % type(val))
269
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
270
    obj = cls(**val_str) # pylint: disable=W0142
271
    return obj
272

    
273
  @staticmethod
274
  def _ContainerToDicts(container):
275
    """Convert the elements of a container to standard python types.
276

277
    This method converts a container with elements derived from
278
    ConfigData to standard python types. If the container is a dict,
279
    we don't touch the keys, only the values.
280

281
    """
282
    if isinstance(container, dict):
283
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
284
    elif isinstance(container, (list, tuple, set, frozenset)):
285
      ret = [elem.ToDict() for elem in container]
286
    else:
287
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
288
                      type(container))
289
    return ret
290

    
291
  @staticmethod
292
  def _ContainerFromDicts(source, c_type, e_type):
293
    """Convert a container from standard python types.
294

295
    This method converts a container with standard python types to
296
    ConfigData objects. If the container is a dict, we don't touch the
297
    keys, only the values.
298

299
    """
300
    if not isinstance(c_type, type):
301
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
302
                      " not a type" % type(c_type))
303
    if source is None:
304
      source = c_type()
305
    if c_type is dict:
306
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
307
    elif c_type in (list, tuple, set, frozenset):
308
      ret = c_type([e_type.FromDict(elem) for elem in source])
309
    else:
310
      raise TypeError("Invalid container type %s passed to"
311
                      " _ContainerFromDicts" % c_type)
312
    return ret
313

    
314
  def Copy(self):
315
    """Makes a deep copy of the current object and its children.
316

317
    """
318
    dict_form = self.ToDict()
319
    clone_obj = self.__class__.FromDict(dict_form)
320
    return clone_obj
321

    
322
  def __repr__(self):
323
    """Implement __repr__ for ConfigObjects."""
324
    return repr(self.ToDict())
325

    
326
  def UpgradeConfig(self):
327
    """Fill defaults for missing configuration values.
328

329
    This method will be called at configuration load time, and its
330
    implementation will be object dependent.
331

332
    """
333
    pass
334

    
335

    
336
class TaggableObject(ConfigObject):
337
  """An generic class supporting tags.
338

339
  """
340
  __slots__ = ["tags"]
341
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
342

    
343
  @classmethod
344
  def ValidateTag(cls, tag):
345
    """Check if a tag is valid.
346

347
    If the tag is invalid, an errors.TagError will be raised. The
348
    function has no return value.
349

350
    """
351
    if not isinstance(tag, basestring):
352
      raise errors.TagError("Invalid tag type (not a string)")
353
    if len(tag) > constants.MAX_TAG_LEN:
354
      raise errors.TagError("Tag too long (>%d characters)" %
355
                            constants.MAX_TAG_LEN)
356
    if not tag:
357
      raise errors.TagError("Tags cannot be empty")
358
    if not cls.VALID_TAG_RE.match(tag):
359
      raise errors.TagError("Tag contains invalid characters")
360

    
361
  def GetTags(self):
362
    """Return the tags list.
363

364
    """
365
    tags = getattr(self, "tags", None)
366
    if tags is None:
367
      tags = self.tags = set()
368
    return tags
369

    
370
  def AddTag(self, tag):
371
    """Add a new tag.
372

373
    """
374
    self.ValidateTag(tag)
375
    tags = self.GetTags()
376
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
377
      raise errors.TagError("Too many tags")
378
    self.GetTags().add(tag)
379

    
380
  def RemoveTag(self, tag):
381
    """Remove a tag.
382

383
    """
384
    self.ValidateTag(tag)
385
    tags = self.GetTags()
386
    try:
387
      tags.remove(tag)
388
    except KeyError:
389
      raise errors.TagError("Tag not found")
390

    
391
  def ToDict(self):
392
    """Taggable-object-specific conversion to standard python types.
393

394
    This replaces the tags set with a list.
395

396
    """
397
    bo = super(TaggableObject, self).ToDict()
398

    
399
    tags = bo.get("tags", None)
400
    if isinstance(tags, set):
401
      bo["tags"] = list(tags)
402
    return bo
403

    
404
  @classmethod
405
  def FromDict(cls, val):
406
    """Custom function for instances.
407

408
    """
409
    obj = super(TaggableObject, cls).FromDict(val)
410
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
411
      obj.tags = set(obj.tags)
412
    return obj
413

    
414

    
415
class MasterNetworkParameters(ConfigObject):
416
  """Network configuration parameters for the master
417

418
  @ivar name: master name
419
  @ivar ip: master IP
420
  @ivar netmask: master netmask
421
  @ivar netdev: master network device
422
  @ivar ip_family: master IP family
423

424
  """
425
  __slots__ = [
426
    "name",
427
    "ip",
428
    "netmask",
429
    "netdev",
430
    "ip_family"
431
    ]
432

    
433

    
434
class ConfigData(ConfigObject):
435
  """Top-level config object."""
436
  __slots__ = [
437
    "version",
438
    "cluster",
439
    "nodes",
440
    "nodegroups",
441
    "instances",
442
    "networks",
443
    "serial_no",
444
    ] + _TIMESTAMPS
445

    
446
  def ToDict(self):
447
    """Custom function for top-level config data.
448

449
    This just replaces the list of instances, nodes and the cluster
450
    with standard python types.
451

452
    """
453
    mydict = super(ConfigData, self).ToDict()
454
    mydict["cluster"] = mydict["cluster"].ToDict()
455
    for key in "nodes", "instances", "nodegroups", "networks":
456
      mydict[key] = self._ContainerToDicts(mydict[key])
457

    
458
    return mydict
459

    
460
  @classmethod
461
  def FromDict(cls, val):
462
    """Custom function for top-level config data
463

464
    """
465
    obj = super(ConfigData, cls).FromDict(val)
466
    obj.cluster = Cluster.FromDict(obj.cluster)
467
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
468
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
469
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
470
    obj.networks = cls._ContainerFromDicts(obj.networks, dict, Network)
471
    return obj
472

    
473
  def HasAnyDiskOfType(self, dev_type):
474
    """Check if in there is at disk of the given type in the configuration.
475

476
    @type dev_type: L{constants.LDS_BLOCK}
477
    @param dev_type: the type to look for
478
    @rtype: boolean
479
    @return: boolean indicating if a disk of the given type was found or not
480

481
    """
482
    for instance in self.instances.values():
483
      for disk in instance.disks:
484
        if disk.IsBasedOnDiskType(dev_type):
485
          return True
486
    return False
487

    
488
  def UpgradeConfig(self):
489
    """Fill defaults for missing configuration values.
490

491
    """
492
    self.cluster.UpgradeConfig()
493
    for node in self.nodes.values():
494
      node.UpgradeConfig()
495
    for instance in self.instances.values():
496
      instance.UpgradeConfig()
497
    if self.nodegroups is None:
498
      self.nodegroups = {}
499
    for nodegroup in self.nodegroups.values():
500
      nodegroup.UpgradeConfig()
501
    if self.cluster.drbd_usermode_helper is None:
502
      # To decide if we set an helper let's check if at least one instance has
503
      # a DRBD disk. This does not cover all the possible scenarios but it
504
      # gives a good approximation.
505
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
506
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
507
    if self.networks is None:
508
      self.networks = {}
509

    
510

    
511
class NIC(ConfigObject):
512
  """Config object representing a network card."""
513
  __slots__ = ["mac", "ip", "network", "nicparams", "netinfo"]
514

    
515
  @classmethod
516
  def CheckParameterSyntax(cls, nicparams):
517
    """Check the given parameters for validity.
518

519
    @type nicparams:  dict
520
    @param nicparams: dictionary with parameter names/value
521
    @raise errors.ConfigurationError: when a parameter is not valid
522

523
    """
524
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
525
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
526
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
527
      raise errors.ConfigurationError(err)
528

    
529
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
530
        not nicparams[constants.NIC_LINK]):
531
      err = "Missing bridged nic link"
532
      raise errors.ConfigurationError(err)
533

    
534

    
535
class Disk(ConfigObject):
536
  """Config object representing a block device."""
537
  __slots__ = ["dev_type", "logical_id", "physical_id",
538
               "children", "iv_name", "size", "mode", "params"]
539

    
540
  def CreateOnSecondary(self):
541
    """Test if this device needs to be created on a secondary node."""
542
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
543

    
544
  def AssembleOnSecondary(self):
545
    """Test if this device needs to be assembled on a secondary node."""
546
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
547

    
548
  def OpenOnSecondary(self):
549
    """Test if this device needs to be opened on a secondary node."""
550
    return self.dev_type in (constants.LD_LV,)
551

    
552
  def StaticDevPath(self):
553
    """Return the device path if this device type has a static one.
554

555
    Some devices (LVM for example) live always at the same /dev/ path,
556
    irrespective of their status. For such devices, we return this
557
    path, for others we return None.
558

559
    @warning: The path returned is not a normalized pathname; callers
560
        should check that it is a valid path.
561

562
    """
563
    if self.dev_type == constants.LD_LV:
564
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
565
    elif self.dev_type == constants.LD_BLOCKDEV:
566
      return self.logical_id[1]
567
    elif self.dev_type == constants.LD_RBD:
568
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
569
    return None
570

    
571
  def ChildrenNeeded(self):
572
    """Compute the needed number of children for activation.
573

574
    This method will return either -1 (all children) or a positive
575
    number denoting the minimum number of children needed for
576
    activation (only mirrored devices will usually return >=0).
577

578
    Currently, only DRBD8 supports diskless activation (therefore we
579
    return 0), for all other we keep the previous semantics and return
580
    -1.
581

582
    """
583
    if self.dev_type == constants.LD_DRBD8:
584
      return 0
585
    return -1
586

    
587
  def IsBasedOnDiskType(self, dev_type):
588
    """Check if the disk or its children are based on the given type.
589

590
    @type dev_type: L{constants.LDS_BLOCK}
591
    @param dev_type: the type to look for
592
    @rtype: boolean
593
    @return: boolean indicating if a device of the given type was found or not
594

595
    """
596
    if self.children:
597
      for child in self.children:
598
        if child.IsBasedOnDiskType(dev_type):
599
          return True
600
    return self.dev_type == dev_type
601

    
602
  def GetNodes(self, node):
603
    """This function returns the nodes this device lives on.
604

605
    Given the node on which the parent of the device lives on (or, in
606
    case of a top-level device, the primary node of the devices'
607
    instance), this function will return a list of nodes on which this
608
    devices needs to (or can) be assembled.
609

610
    """
611
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
612
                         constants.LD_BLOCKDEV, constants.LD_RBD]:
613
      result = [node]
614
    elif self.dev_type in constants.LDS_DRBD:
615
      result = [self.logical_id[0], self.logical_id[1]]
616
      if node not in result:
617
        raise errors.ConfigurationError("DRBD device passed unknown node")
618
    else:
619
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
620
    return result
621

    
622
  def ComputeNodeTree(self, parent_node):
623
    """Compute the node/disk tree for this disk and its children.
624

625
    This method, given the node on which the parent disk lives, will
626
    return the list of all (node, disk) pairs which describe the disk
627
    tree in the most compact way. For example, a drbd/lvm stack
628
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
629
    which represents all the top-level devices on the nodes.
630

631
    """
632
    my_nodes = self.GetNodes(parent_node)
633
    result = [(node, self) for node in my_nodes]
634
    if not self.children:
635
      # leaf device
636
      return result
637
    for node in my_nodes:
638
      for child in self.children:
639
        child_result = child.ComputeNodeTree(node)
640
        if len(child_result) == 1:
641
          # child (and all its descendants) is simple, doesn't split
642
          # over multiple hosts, so we don't need to describe it, our
643
          # own entry for this node describes it completely
644
          continue
645
        else:
646
          # check if child nodes differ from my nodes; note that
647
          # subdisk can differ from the child itself, and be instead
648
          # one of its descendants
649
          for subnode, subdisk in child_result:
650
            if subnode not in my_nodes:
651
              result.append((subnode, subdisk))
652
            # otherwise child is under our own node, so we ignore this
653
            # entry (but probably the other results in the list will
654
            # be different)
655
    return result
656

    
657
  def ComputeGrowth(self, amount):
658
    """Compute the per-VG growth requirements.
659

660
    This only works for VG-based disks.
661

662
    @type amount: integer
663
    @param amount: the desired increase in (user-visible) disk space
664
    @rtype: dict
665
    @return: a dictionary of volume-groups and the required size
666

667
    """
668
    if self.dev_type == constants.LD_LV:
669
      return {self.logical_id[0]: amount}
670
    elif self.dev_type == constants.LD_DRBD8:
671
      if self.children:
672
        return self.children[0].ComputeGrowth(amount)
673
      else:
674
        return {}
675
    else:
676
      # Other disk types do not require VG space
677
      return {}
678

    
679
  def RecordGrow(self, amount):
680
    """Update the size of this disk after growth.
681

682
    This method recurses over the disks's children and updates their
683
    size correspondigly. The method needs to be kept in sync with the
684
    actual algorithms from bdev.
685

686
    """
687
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
688
                         constants.LD_RBD):
689
      self.size += amount
690
    elif self.dev_type == constants.LD_DRBD8:
691
      if self.children:
692
        self.children[0].RecordGrow(amount)
693
      self.size += amount
694
    else:
695
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
696
                                   " disk type %s" % self.dev_type)
697

    
698
  def Update(self, size=None, mode=None):
699
    """Apply changes to size and mode.
700

701
    """
702
    if self.dev_type == constants.LD_DRBD8:
703
      if self.children:
704
        self.children[0].Update(size=size, mode=mode)
705
    else:
706
      assert not self.children
707

    
708
    if size is not None:
709
      self.size = size
710
    if mode is not None:
711
      self.mode = mode
712

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
858
  @staticmethod
859
  def ComputeLDParams(disk_template, disk_params):
860
    """Computes Logical Disk parameters from Disk Template parameters.
861

862
    @type disk_template: string
863
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
864
    @type disk_params: dict
865
    @param disk_params: disk template parameters;
866
                        dict(template_name -> parameters
867
    @rtype: list(dict)
868
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
869
      contains the LD parameters of the node. The tree is flattened in-order.
870

871
    """
872
    if disk_template not in constants.DISK_TEMPLATES:
873
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
874

    
875
    assert disk_template in disk_params
876

    
877
    result = list()
878
    dt_params = disk_params[disk_template]
879
    if disk_template == constants.DT_DRBD8:
880
      drbd_params = {
881
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
882
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
883
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
884
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
885
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
886
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
887
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
888
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
889
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
890
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
891
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
892
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
893
        }
894

    
895
      drbd_params = \
896
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8],
897
                 drbd_params)
898

    
899
      result.append(drbd_params)
900

    
901
      # data LV
902
      data_params = {
903
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
904
        }
905
      data_params = \
906
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
907
                 data_params)
908
      result.append(data_params)
909

    
910
      # metadata LV
911
      meta_params = {
912
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
913
        }
914
      meta_params = \
915
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
916
                 meta_params)
917
      result.append(meta_params)
918

    
919
    elif (disk_template == constants.DT_FILE or
920
          disk_template == constants.DT_SHARED_FILE):
921
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
922

    
923
    elif disk_template == constants.DT_PLAIN:
924
      params = {
925
        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
926
        }
927
      params = \
928
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
929
                 params)
930
      result.append(params)
931

    
932
    elif disk_template == constants.DT_BLOCK:
933
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
934

    
935
    elif disk_template == constants.DT_RBD:
936
      params = {
937
        constants.LDP_POOL: dt_params[constants.RBD_POOL]
938
        }
939
      params = \
940
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD],
941
                 params)
942
      result.append(params)
943

    
944
    return result
945

    
946

    
947
class InstancePolicy(ConfigObject):
948
  """Config object representing instance policy limits dictionary.
949

950

951
  Note that this object is not actually used in the config, it's just
952
  used as a placeholder for a few functions.
953

954
  """
955
  @classmethod
956
  def CheckParameterSyntax(cls, ipolicy):
957
    """ Check the instance policy for validity.
958

959
    """
960
    for param in constants.ISPECS_PARAMETERS:
961
      InstancePolicy.CheckISpecSyntax(ipolicy, param)
962
    if constants.IPOLICY_DTS in ipolicy:
963
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
964
    for key in constants.IPOLICY_PARAMETERS:
965
      if key in ipolicy:
966
        InstancePolicy.CheckParameter(key, ipolicy[key])
967
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
968
    if wrong_keys:
969
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
970
                                      utils.CommaJoin(wrong_keys))
971

    
972
  @classmethod
973
  def CheckISpecSyntax(cls, ipolicy, name):
974
    """Check the instance policy for validity on a given key.
975

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

979
    @type ipolicy: dict
980
    @param ipolicy: dictionary with min, max, std specs
981
    @type name: string
982
    @param name: what are the limits for
983
    @raise errors.ConfigureError: when specs for given name are not valid
984

985
    """
986
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
987
    std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
988
    max_v = ipolicy[constants.ISPECS_MAX].get(name, std_v)
989
    err = ("Invalid specification of min/max/std values for %s: %s/%s/%s" %
990
           (name,
991
            ipolicy[constants.ISPECS_MIN].get(name, "-"),
992
            ipolicy[constants.ISPECS_MAX].get(name, "-"),
993
            ipolicy[constants.ISPECS_STD].get(name, "-")))
994
    if min_v > std_v or std_v > max_v:
995
      raise errors.ConfigurationError(err)
996

    
997
  @classmethod
998
  def CheckDiskTemplates(cls, disk_templates):
999
    """Checks the disk templates for validity.
1000

1001
    """
1002
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
1003
    if wrong:
1004
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
1005
                                      utils.CommaJoin(wrong))
1006

    
1007
  @classmethod
1008
  def CheckParameter(cls, key, value):
1009
    """Checks a parameter.
1010

1011
    Currently we expect all parameters to be float values.
1012

1013
    """
1014
    try:
1015
      float(value)
1016
    except (TypeError, ValueError), err:
1017
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
1018
                                      " '%s', error: %s" % (key, value, err))
1019

    
1020

    
1021
class Instance(TaggableObject):
1022
  """Config object representing an instance."""
1023
  __slots__ = [
1024
    "name",
1025
    "primary_node",
1026
    "os",
1027
    "hypervisor",
1028
    "hvparams",
1029
    "beparams",
1030
    "osparams",
1031
    "admin_state",
1032
    "nics",
1033
    "disks",
1034
    "disk_template",
1035
    "network_port",
1036
    "serial_no",
1037
    ] + _TIMESTAMPS + _UUID
1038

    
1039
  def _ComputeSecondaryNodes(self):
1040
    """Compute the list of secondary nodes.
1041

1042
    This is a simple wrapper over _ComputeAllNodes.
1043

1044
    """
1045
    all_nodes = set(self._ComputeAllNodes())
1046
    all_nodes.discard(self.primary_node)
1047
    return tuple(all_nodes)
1048

    
1049
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1050
                             "List of secondary nodes")
1051

    
1052
  def _ComputeAllNodes(self):
1053
    """Compute the list of all nodes.
1054

1055
    Since the data is already there (in the drbd disks), keeping it as
1056
    a separate normal attribute is redundant and if not properly
1057
    synchronised can cause problems. Thus it's better to compute it
1058
    dynamically.
1059

1060
    """
1061
    def _Helper(nodes, device):
1062
      """Recursively computes nodes given a top device."""
1063
      if device.dev_type in constants.LDS_DRBD:
1064
        nodea, nodeb = device.logical_id[:2]
1065
        nodes.add(nodea)
1066
        nodes.add(nodeb)
1067
      if device.children:
1068
        for child in device.children:
1069
          _Helper(nodes, child)
1070

    
1071
    all_nodes = set()
1072
    all_nodes.add(self.primary_node)
1073
    for device in self.disks:
1074
      _Helper(all_nodes, device)
1075
    return tuple(all_nodes)
1076

    
1077
  all_nodes = property(_ComputeAllNodes, None, None,
1078
                       "List of all nodes of the instance")
1079

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

1083
    This function figures out what logical volumes should belong on
1084
    which nodes, recursing through a device tree.
1085

1086
    @param lvmap: optional dictionary to receive the
1087
        'node' : ['lv', ...] data.
1088

1089
    @return: None if lvmap arg is given, otherwise, a dictionary of
1090
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1091
        volumeN is of the form "vg_name/lv_name", compatible with
1092
        GetVolumeList()
1093

1094
    """
1095
    if node == None:
1096
      node = self.primary_node
1097

    
1098
    if lvmap is None:
1099
      lvmap = {
1100
        node: [],
1101
        }
1102
      ret = lvmap
1103
    else:
1104
      if not node in lvmap:
1105
        lvmap[node] = []
1106
      ret = None
1107

    
1108
    if not devs:
1109
      devs = self.disks
1110

    
1111
    for dev in devs:
1112
      if dev.dev_type == constants.LD_LV:
1113
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1114

    
1115
      elif dev.dev_type in constants.LDS_DRBD:
1116
        if dev.children:
1117
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1118
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1119

    
1120
      elif dev.children:
1121
        self.MapLVsByNode(lvmap, dev.children, node)
1122

    
1123
    return ret
1124

    
1125
  def FindDisk(self, idx):
1126
    """Find a disk given having a specified index.
1127

1128
    This is just a wrapper that does validation of the index.
1129

1130
    @type idx: int
1131
    @param idx: the disk index
1132
    @rtype: L{Disk}
1133
    @return: the corresponding disk
1134
    @raise errors.OpPrereqError: when the given index is not valid
1135

1136
    """
1137
    try:
1138
      idx = int(idx)
1139
      return self.disks[idx]
1140
    except (TypeError, ValueError), err:
1141
      raise errors.OpPrereqError("Invalid disk index: '%s'" % str(err),
1142
                                 errors.ECODE_INVAL)
1143
    except IndexError:
1144
      raise errors.OpPrereqError("Invalid disk index: %d (instace has disks"
1145
                                 " 0 to %d" % (idx, len(self.disks) - 1),
1146
                                 errors.ECODE_INVAL)
1147

    
1148
  def ToDict(self):
1149
    """Instance-specific conversion to standard python types.
1150

1151
    This replaces the children lists of objects with lists of standard
1152
    python types.
1153

1154
    """
1155
    bo = super(Instance, self).ToDict()
1156

    
1157
    for attr in "nics", "disks":
1158
      alist = bo.get(attr, None)
1159
      if alist:
1160
        nlist = self._ContainerToDicts(alist)
1161
      else:
1162
        nlist = []
1163
      bo[attr] = nlist
1164
    return bo
1165

    
1166
  @classmethod
1167
  def FromDict(cls, val):
1168
    """Custom function for instances.
1169

1170
    """
1171
    if "admin_state" not in val:
1172
      if val.get("admin_up", False):
1173
        val["admin_state"] = constants.ADMINST_UP
1174
      else:
1175
        val["admin_state"] = constants.ADMINST_DOWN
1176
    if "admin_up" in val:
1177
      del val["admin_up"]
1178
    obj = super(Instance, cls).FromDict(val)
1179
    obj.nics = cls._ContainerFromDicts(obj.nics, list, NIC)
1180
    obj.disks = cls._ContainerFromDicts(obj.disks, list, Disk)
1181
    return obj
1182

    
1183
  def UpgradeConfig(self):
1184
    """Fill defaults for missing configuration values.
1185

1186
    """
1187
    for nic in self.nics:
1188
      nic.UpgradeConfig()
1189
    for disk in self.disks:
1190
      disk.UpgradeConfig()
1191
    if self.hvparams:
1192
      for key in constants.HVC_GLOBALS:
1193
        try:
1194
          del self.hvparams[key]
1195
        except KeyError:
1196
          pass
1197
    if self.osparams is None:
1198
      self.osparams = {}
1199
    UpgradeBeParams(self.beparams)
1200

    
1201

    
1202
class OS(ConfigObject):
1203
  """Config object representing an operating system.
1204

1205
  @type supported_parameters: list
1206
  @ivar supported_parameters: a list of tuples, name and description,
1207
      containing the supported parameters by this OS
1208

1209
  @type VARIANT_DELIM: string
1210
  @cvar VARIANT_DELIM: the variant delimiter
1211

1212
  """
1213
  __slots__ = [
1214
    "name",
1215
    "path",
1216
    "api_versions",
1217
    "create_script",
1218
    "export_script",
1219
    "import_script",
1220
    "rename_script",
1221
    "verify_script",
1222
    "supported_variants",
1223
    "supported_parameters",
1224
    ]
1225

    
1226
  VARIANT_DELIM = "+"
1227

    
1228
  @classmethod
1229
  def SplitNameVariant(cls, name):
1230
    """Splits the name into the proper name and variant.
1231

1232
    @param name: the OS (unprocessed) name
1233
    @rtype: list
1234
    @return: a list of two elements; if the original name didn't
1235
        contain a variant, it's returned as an empty string
1236

1237
    """
1238
    nv = name.split(cls.VARIANT_DELIM, 1)
1239
    if len(nv) == 1:
1240
      nv.append("")
1241
    return nv
1242

    
1243
  @classmethod
1244
  def GetName(cls, name):
1245
    """Returns the proper name of the os (without the variant).
1246

1247
    @param name: the OS (unprocessed) name
1248

1249
    """
1250
    return cls.SplitNameVariant(name)[0]
1251

    
1252
  @classmethod
1253
  def GetVariant(cls, name):
1254
    """Returns the variant the os (without the base name).
1255

1256
    @param name: the OS (unprocessed) name
1257

1258
    """
1259
    return cls.SplitNameVariant(name)[1]
1260

    
1261

    
1262
class NodeHvState(ConfigObject):
1263
  """Hypvervisor state on a node.
1264

1265
  @ivar mem_total: Total amount of memory
1266
  @ivar mem_node: Memory used by, or reserved for, the node itself (not always
1267
    available)
1268
  @ivar mem_hv: Memory used by hypervisor or lost due to instance allocation
1269
    rounding
1270
  @ivar mem_inst: Memory used by instances living on node
1271
  @ivar cpu_total: Total node CPU core count
1272
  @ivar cpu_node: Number of CPU cores reserved for the node itself
1273

1274
  """
1275
  __slots__ = [
1276
    "mem_total",
1277
    "mem_node",
1278
    "mem_hv",
1279
    "mem_inst",
1280
    "cpu_total",
1281
    "cpu_node",
1282
    ] + _TIMESTAMPS
1283

    
1284

    
1285
class NodeDiskState(ConfigObject):
1286
  """Disk state on a node.
1287

1288
  """
1289
  __slots__ = [
1290
    "total",
1291
    "reserved",
1292
    "overhead",
1293
    ] + _TIMESTAMPS
1294

    
1295

    
1296
class Node(TaggableObject):
1297
  """Config object representing a node.
1298

1299
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1300
  @ivar hv_state_static: Hypervisor state overriden by user
1301
  @ivar disk_state: Disk state (e.g. free space)
1302
  @ivar disk_state_static: Disk state overriden by user
1303

1304
  """
1305
  __slots__ = [
1306
    "name",
1307
    "primary_ip",
1308
    "secondary_ip",
1309
    "serial_no",
1310
    "master_candidate",
1311
    "offline",
1312
    "drained",
1313
    "group",
1314
    "master_capable",
1315
    "vm_capable",
1316
    "ndparams",
1317
    "powered",
1318
    "hv_state",
1319
    "hv_state_static",
1320
    "disk_state",
1321
    "disk_state_static",
1322
    ] + _TIMESTAMPS + _UUID
1323

    
1324
  def UpgradeConfig(self):
1325
    """Fill defaults for missing configuration values.
1326

1327
    """
1328
    # pylint: disable=E0203
1329
    # because these are "defined" via slots, not manually
1330
    if self.master_capable is None:
1331
      self.master_capable = True
1332

    
1333
    if self.vm_capable is None:
1334
      self.vm_capable = True
1335

    
1336
    if self.ndparams is None:
1337
      self.ndparams = {}
1338

    
1339
    if self.powered is None:
1340
      self.powered = True
1341

    
1342
  def ToDict(self):
1343
    """Custom function for serializing.
1344

1345
    """
1346
    data = super(Node, self).ToDict()
1347

    
1348
    hv_state = data.get("hv_state", None)
1349
    if hv_state is not None:
1350
      data["hv_state"] = self._ContainerToDicts(hv_state)
1351

    
1352
    disk_state = data.get("disk_state", None)
1353
    if disk_state is not None:
1354
      data["disk_state"] = \
1355
        dict((key, self._ContainerToDicts(value))
1356
             for (key, value) in disk_state.items())
1357

    
1358
    return data
1359

    
1360
  @classmethod
1361
  def FromDict(cls, val):
1362
    """Custom function for deserializing.
1363

1364
    """
1365
    obj = super(Node, cls).FromDict(val)
1366

    
1367
    if obj.hv_state is not None:
1368
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1369

    
1370
    if obj.disk_state is not None:
1371
      obj.disk_state = \
1372
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1373
             for (key, value) in obj.disk_state.items())
1374

    
1375
    return obj
1376

    
1377

    
1378
class NodeGroup(TaggableObject):
1379
  """Config object representing a node group."""
1380
  __slots__ = [
1381
    "name",
1382
    "members",
1383
    "ndparams",
1384
    "diskparams",
1385
    "ipolicy",
1386
    "serial_no",
1387
    "hv_state_static",
1388
    "disk_state_static",
1389
    "alloc_policy",
1390
    "networks",
1391
    ] + _TIMESTAMPS + _UUID
1392

    
1393
  def ToDict(self):
1394
    """Custom function for nodegroup.
1395

1396
    This discards the members object, which gets recalculated and is only kept
1397
    in memory.
1398

1399
    """
1400
    mydict = super(NodeGroup, self).ToDict()
1401
    del mydict["members"]
1402
    return mydict
1403

    
1404
  @classmethod
1405
  def FromDict(cls, val):
1406
    """Custom function for nodegroup.
1407

1408
    The members slot is initialized to an empty list, upon deserialization.
1409

1410
    """
1411
    obj = super(NodeGroup, cls).FromDict(val)
1412
    obj.members = []
1413
    return obj
1414

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

1418
    """
1419
    if self.ndparams is None:
1420
      self.ndparams = {}
1421

    
1422
    if self.serial_no is None:
1423
      self.serial_no = 1
1424

    
1425
    if self.alloc_policy is None:
1426
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1427

    
1428
    # We only update mtime, and not ctime, since we would not be able
1429
    # to provide a correct value for creation time.
1430
    if self.mtime is None:
1431
      self.mtime = time.time()
1432

    
1433
    if self.diskparams is None:
1434
      self.diskparams = {}
1435
    if self.ipolicy is None:
1436
      self.ipolicy = MakeEmptyIPolicy()
1437

    
1438
    if self.networks is None:
1439
      self.networks = {}
1440

    
1441
  def FillND(self, node):
1442
    """Return filled out ndparams for L{objects.Node}
1443

1444
    @type node: L{objects.Node}
1445
    @param node: A Node object to fill
1446
    @return a copy of the node's ndparams with defaults filled
1447

1448
    """
1449
    return self.SimpleFillND(node.ndparams)
1450

    
1451
  def SimpleFillND(self, ndparams):
1452
    """Fill a given ndparams dict with defaults.
1453

1454
    @type ndparams: dict
1455
    @param ndparams: the dict to fill
1456
    @rtype: dict
1457
    @return: a copy of the passed in ndparams with missing keys filled
1458
        from the node group defaults
1459

1460
    """
1461
    return FillDict(self.ndparams, ndparams)
1462

    
1463

    
1464
class Cluster(TaggableObject):
1465
  """Config object representing the cluster."""
1466
  __slots__ = [
1467
    "serial_no",
1468
    "rsahostkeypub",
1469
    "highest_used_port",
1470
    "tcpudp_port_pool",
1471
    "mac_prefix",
1472
    "volume_group_name",
1473
    "reserved_lvs",
1474
    "drbd_usermode_helper",
1475
    "default_bridge",
1476
    "default_hypervisor",
1477
    "master_node",
1478
    "master_ip",
1479
    "master_netdev",
1480
    "master_netmask",
1481
    "use_external_mip_script",
1482
    "cluster_name",
1483
    "file_storage_dir",
1484
    "shared_file_storage_dir",
1485
    "enabled_hypervisors",
1486
    "hvparams",
1487
    "ipolicy",
1488
    "os_hvp",
1489
    "beparams",
1490
    "osparams",
1491
    "nicparams",
1492
    "ndparams",
1493
    "diskparams",
1494
    "candidate_pool_size",
1495
    "modify_etc_hosts",
1496
    "modify_ssh_setup",
1497
    "maintain_node_health",
1498
    "uid_pool",
1499
    "default_iallocator",
1500
    "hidden_os",
1501
    "blacklisted_os",
1502
    "primary_ip_family",
1503
    "prealloc_wipe_disks",
1504
    "hv_state_static",
1505
    "disk_state_static",
1506
    ] + _TIMESTAMPS + _UUID
1507

    
1508
  def UpgradeConfig(self):
1509
    """Fill defaults for missing configuration values.
1510

1511
    """
1512
    # pylint: disable=E0203
1513
    # because these are "defined" via slots, not manually
1514
    if self.hvparams is None:
1515
      self.hvparams = constants.HVC_DEFAULTS
1516
    else:
1517
      for hypervisor in self.hvparams:
1518
        self.hvparams[hypervisor] = FillDict(
1519
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1520

    
1521
    if self.os_hvp is None:
1522
      self.os_hvp = {}
1523

    
1524
    # osparams added before 2.2
1525
    if self.osparams is None:
1526
      self.osparams = {}
1527

    
1528
    self.ndparams = UpgradeNDParams(self.ndparams)
1529

    
1530
    self.beparams = UpgradeGroupedParams(self.beparams,
1531
                                         constants.BEC_DEFAULTS)
1532
    for beparams_group in self.beparams:
1533
      UpgradeBeParams(self.beparams[beparams_group])
1534

    
1535
    migrate_default_bridge = not self.nicparams
1536
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1537
                                          constants.NICC_DEFAULTS)
1538
    if migrate_default_bridge:
1539
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1540
        self.default_bridge
1541

    
1542
    if self.modify_etc_hosts is None:
1543
      self.modify_etc_hosts = True
1544

    
1545
    if self.modify_ssh_setup is None:
1546
      self.modify_ssh_setup = True
1547

    
1548
    # default_bridge is no longer used in 2.1. The slot is left there to
1549
    # support auto-upgrading. It can be removed once we decide to deprecate
1550
    # upgrading straight from 2.0.
1551
    if self.default_bridge is not None:
1552
      self.default_bridge = None
1553

    
1554
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1555
    # code can be removed once upgrading straight from 2.0 is deprecated.
1556
    if self.default_hypervisor is not None:
1557
      self.enabled_hypervisors = ([self.default_hypervisor] +
1558
        [hvname for hvname in self.enabled_hypervisors
1559
         if hvname != self.default_hypervisor])
1560
      self.default_hypervisor = None
1561

    
1562
    # maintain_node_health added after 2.1.1
1563
    if self.maintain_node_health is None:
1564
      self.maintain_node_health = False
1565

    
1566
    if self.uid_pool is None:
1567
      self.uid_pool = []
1568

    
1569
    if self.default_iallocator is None:
1570
      self.default_iallocator = ""
1571

    
1572
    # reserved_lvs added before 2.2
1573
    if self.reserved_lvs is None:
1574
      self.reserved_lvs = []
1575

    
1576
    # hidden and blacklisted operating systems added before 2.2.1
1577
    if self.hidden_os is None:
1578
      self.hidden_os = []
1579

    
1580
    if self.blacklisted_os is None:
1581
      self.blacklisted_os = []
1582

    
1583
    # primary_ip_family added before 2.3
1584
    if self.primary_ip_family is None:
1585
      self.primary_ip_family = AF_INET
1586

    
1587
    if self.master_netmask is None:
1588
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1589
      self.master_netmask = ipcls.iplen
1590

    
1591
    if self.prealloc_wipe_disks is None:
1592
      self.prealloc_wipe_disks = False
1593

    
1594
    # shared_file_storage_dir added before 2.5
1595
    if self.shared_file_storage_dir is None:
1596
      self.shared_file_storage_dir = ""
1597

    
1598
    if self.use_external_mip_script is None:
1599
      self.use_external_mip_script = False
1600

    
1601
    if self.diskparams:
1602
      self.diskparams = UpgradeDiskParams(self.diskparams)
1603
    else:
1604
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1605

    
1606
    # instance policy added before 2.6
1607
    if self.ipolicy is None:
1608
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1609
    else:
1610
      # we can either make sure to upgrade the ipolicy always, or only
1611
      # do it in some corner cases (e.g. missing keys); note that this
1612
      # will break any removal of keys from the ipolicy dict
1613
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1614

    
1615
  @property
1616
  def primary_hypervisor(self):
1617
    """The first hypervisor is the primary.
1618

1619
    Useful, for example, for L{Node}'s hv/disk state.
1620

1621
    """
1622
    return self.enabled_hypervisors[0]
1623

    
1624
  def ToDict(self):
1625
    """Custom function for cluster.
1626

1627
    """
1628
    mydict = super(Cluster, self).ToDict()
1629
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1630
    return mydict
1631

    
1632
  @classmethod
1633
  def FromDict(cls, val):
1634
    """Custom function for cluster.
1635

1636
    """
1637
    obj = super(Cluster, cls).FromDict(val)
1638
    if not isinstance(obj.tcpudp_port_pool, set):
1639
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1640
    return obj
1641

    
1642
  def SimpleFillDP(self, diskparams):
1643
    """Fill a given diskparams dict with cluster defaults.
1644

1645
    @param diskparams: The diskparams
1646
    @return: The defaults dict
1647

1648
    """
1649
    return FillDiskParams(self.diskparams, diskparams)
1650

    
1651
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1652
    """Get the default hypervisor parameters for the cluster.
1653

1654
    @param hypervisor: the hypervisor name
1655
    @param os_name: if specified, we'll also update the defaults for this OS
1656
    @param skip_keys: if passed, list of keys not to use
1657
    @return: the defaults dict
1658

1659
    """
1660
    if skip_keys is None:
1661
      skip_keys = []
1662

    
1663
    fill_stack = [self.hvparams.get(hypervisor, {})]
1664
    if os_name is not None:
1665
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1666
      fill_stack.append(os_hvp)
1667

    
1668
    ret_dict = {}
1669
    for o_dict in fill_stack:
1670
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1671

    
1672
    return ret_dict
1673

    
1674
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1675
    """Fill a given hvparams dict with cluster defaults.
1676

1677
    @type hv_name: string
1678
    @param hv_name: the hypervisor to use
1679
    @type os_name: string
1680
    @param os_name: the OS to use for overriding the hypervisor defaults
1681
    @type skip_globals: boolean
1682
    @param skip_globals: if True, the global hypervisor parameters will
1683
        not be filled
1684
    @rtype: dict
1685
    @return: a copy of the given hvparams with missing keys filled from
1686
        the cluster defaults
1687

1688
    """
1689
    if skip_globals:
1690
      skip_keys = constants.HVC_GLOBALS
1691
    else:
1692
      skip_keys = []
1693

    
1694
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1695
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1696

    
1697
  def FillHV(self, instance, skip_globals=False):
1698
    """Fill an instance's hvparams dict with cluster defaults.
1699

1700
    @type instance: L{objects.Instance}
1701
    @param instance: the instance parameter to fill
1702
    @type skip_globals: boolean
1703
    @param skip_globals: if True, the global hypervisor parameters will
1704
        not be filled
1705
    @rtype: dict
1706
    @return: a copy of the instance's hvparams with missing keys filled from
1707
        the cluster defaults
1708

1709
    """
1710
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1711
                             instance.hvparams, skip_globals)
1712

    
1713
  def SimpleFillBE(self, beparams):
1714
    """Fill a given beparams dict with cluster defaults.
1715

1716
    @type beparams: dict
1717
    @param beparams: the dict to fill
1718
    @rtype: dict
1719
    @return: a copy of the passed in beparams with missing keys filled
1720
        from the cluster defaults
1721

1722
    """
1723
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1724

    
1725
  def FillBE(self, instance):
1726
    """Fill an instance's beparams dict with cluster defaults.
1727

1728
    @type instance: L{objects.Instance}
1729
    @param instance: the instance parameter to fill
1730
    @rtype: dict
1731
    @return: a copy of the instance's beparams with missing keys filled from
1732
        the cluster defaults
1733

1734
    """
1735
    return self.SimpleFillBE(instance.beparams)
1736

    
1737
  def SimpleFillNIC(self, nicparams):
1738
    """Fill a given nicparams dict with cluster defaults.
1739

1740
    @type nicparams: dict
1741
    @param nicparams: the dict to fill
1742
    @rtype: dict
1743
    @return: a copy of the passed in nicparams with missing keys filled
1744
        from the cluster defaults
1745

1746
    """
1747
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1748

    
1749
  def SimpleFillOS(self, os_name, os_params):
1750
    """Fill an instance's osparams dict with cluster defaults.
1751

1752
    @type os_name: string
1753
    @param os_name: the OS name to use
1754
    @type os_params: dict
1755
    @param os_params: the dict to fill with default values
1756
    @rtype: dict
1757
    @return: a copy of the instance's osparams with missing keys filled from
1758
        the cluster defaults
1759

1760
    """
1761
    name_only = os_name.split("+", 1)[0]
1762
    # base OS
1763
    result = self.osparams.get(name_only, {})
1764
    # OS with variant
1765
    result = FillDict(result, self.osparams.get(os_name, {}))
1766
    # specified params
1767
    return FillDict(result, os_params)
1768

    
1769
  @staticmethod
1770
  def SimpleFillHvState(hv_state):
1771
    """Fill an hv_state sub dict with cluster defaults.
1772

1773
    """
1774
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1775

    
1776
  @staticmethod
1777
  def SimpleFillDiskState(disk_state):
1778
    """Fill an disk_state sub dict with cluster defaults.
1779

1780
    """
1781
    return FillDict(constants.DS_DEFAULTS, disk_state)
1782

    
1783
  def FillND(self, node, nodegroup):
1784
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1785

1786
    @type node: L{objects.Node}
1787
    @param node: A Node object to fill
1788
    @type nodegroup: L{objects.NodeGroup}
1789
    @param nodegroup: A Node object to fill
1790
    @return a copy of the node's ndparams with defaults filled
1791

1792
    """
1793
    return self.SimpleFillND(nodegroup.FillND(node))
1794

    
1795
  def SimpleFillND(self, ndparams):
1796
    """Fill a given ndparams dict with defaults.
1797

1798
    @type ndparams: dict
1799
    @param ndparams: the dict to fill
1800
    @rtype: dict
1801
    @return: a copy of the passed in ndparams with missing keys filled
1802
        from the cluster defaults
1803

1804
    """
1805
    return FillDict(self.ndparams, ndparams)
1806

    
1807
  def SimpleFillIPolicy(self, ipolicy):
1808
    """ Fill instance policy dict with defaults.
1809

1810
    @type ipolicy: dict
1811
    @param ipolicy: the dict to fill
1812
    @rtype: dict
1813
    @return: a copy of passed ipolicy with missing keys filled from
1814
      the cluster defaults
1815

1816
    """
1817
    return FillIPolicy(self.ipolicy, ipolicy)
1818

    
1819

    
1820
class BlockDevStatus(ConfigObject):
1821
  """Config object representing the status of a block device."""
1822
  __slots__ = [
1823
    "dev_path",
1824
    "major",
1825
    "minor",
1826
    "sync_percent",
1827
    "estimated_time",
1828
    "is_degraded",
1829
    "ldisk_status",
1830
    ]
1831

    
1832

    
1833
class ImportExportStatus(ConfigObject):
1834
  """Config object representing the status of an import or export."""
1835
  __slots__ = [
1836
    "recent_output",
1837
    "listen_port",
1838
    "connected",
1839
    "progress_mbytes",
1840
    "progress_throughput",
1841
    "progress_eta",
1842
    "progress_percent",
1843
    "exit_status",
1844
    "error_message",
1845
    ] + _TIMESTAMPS
1846

    
1847

    
1848
class ImportExportOptions(ConfigObject):
1849
  """Options for import/export daemon
1850

1851
  @ivar key_name: X509 key name (None for cluster certificate)
1852
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1853
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1854
  @ivar magic: Used to ensure the connection goes to the right disk
1855
  @ivar ipv6: Whether to use IPv6
1856
  @ivar connect_timeout: Number of seconds for establishing connection
1857

1858
  """
1859
  __slots__ = [
1860
    "key_name",
1861
    "ca_pem",
1862
    "compress",
1863
    "magic",
1864
    "ipv6",
1865
    "connect_timeout",
1866
    ]
1867

    
1868

    
1869
class ConfdRequest(ConfigObject):
1870
  """Object holding a confd request.
1871

1872
  @ivar protocol: confd protocol version
1873
  @ivar type: confd query type
1874
  @ivar query: query request
1875
  @ivar rsalt: requested reply salt
1876

1877
  """
1878
  __slots__ = [
1879
    "protocol",
1880
    "type",
1881
    "query",
1882
    "rsalt",
1883
    ]
1884

    
1885

    
1886
class ConfdReply(ConfigObject):
1887
  """Object holding a confd reply.
1888

1889
  @ivar protocol: confd protocol version
1890
  @ivar status: reply status code (ok, error)
1891
  @ivar answer: confd query reply
1892
  @ivar serial: configuration serial number
1893

1894
  """
1895
  __slots__ = [
1896
    "protocol",
1897
    "status",
1898
    "answer",
1899
    "serial",
1900
    ]
1901

    
1902

    
1903
class QueryFieldDefinition(ConfigObject):
1904
  """Object holding a query field definition.
1905

1906
  @ivar name: Field name
1907
  @ivar title: Human-readable title
1908
  @ivar kind: Field type
1909
  @ivar doc: Human-readable description
1910

1911
  """
1912
  __slots__ = [
1913
    "name",
1914
    "title",
1915
    "kind",
1916
    "doc",
1917
    ]
1918

    
1919

    
1920
class _QueryResponseBase(ConfigObject):
1921
  __slots__ = [
1922
    "fields",
1923
    ]
1924

    
1925
  def ToDict(self):
1926
    """Custom function for serializing.
1927

1928
    """
1929
    mydict = super(_QueryResponseBase, self).ToDict()
1930
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1931
    return mydict
1932

    
1933
  @classmethod
1934
  def FromDict(cls, val):
1935
    """Custom function for de-serializing.
1936

1937
    """
1938
    obj = super(_QueryResponseBase, cls).FromDict(val)
1939
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1940
    return obj
1941

    
1942

    
1943
class QueryResponse(_QueryResponseBase):
1944
  """Object holding the response to a query.
1945

1946
  @ivar fields: List of L{QueryFieldDefinition} objects
1947
  @ivar data: Requested data
1948

1949
  """
1950
  __slots__ = [
1951
    "data",
1952
    ]
1953

    
1954

    
1955
class QueryFieldsRequest(ConfigObject):
1956
  """Object holding a request for querying available fields.
1957

1958
  """
1959
  __slots__ = [
1960
    "what",
1961
    "fields",
1962
    ]
1963

    
1964

    
1965
class QueryFieldsResponse(_QueryResponseBase):
1966
  """Object holding the response to a query for fields.
1967

1968
  @ivar fields: List of L{QueryFieldDefinition} objects
1969

1970
  """
1971
  __slots__ = [
1972
    ]
1973

    
1974

    
1975
class MigrationStatus(ConfigObject):
1976
  """Object holding the status of a migration.
1977

1978
  """
1979
  __slots__ = [
1980
    "status",
1981
    "transferred_ram",
1982
    "total_ram",
1983
    ]
1984

    
1985

    
1986
class InstanceConsole(ConfigObject):
1987
  """Object describing how to access the console of an instance.
1988

1989
  """
1990
  __slots__ = [
1991
    "instance",
1992
    "kind",
1993
    "message",
1994
    "host",
1995
    "port",
1996
    "user",
1997
    "command",
1998
    "display",
1999
    ]
2000

    
2001
  def Validate(self):
2002
    """Validates contents of this object.
2003

2004
    """
2005
    assert self.kind in constants.CONS_ALL, "Unknown console type"
2006
    assert self.instance, "Missing instance name"
2007
    assert self.message or self.kind in [constants.CONS_SSH,
2008
                                         constants.CONS_SPICE,
2009
                                         constants.CONS_VNC]
2010
    assert self.host or self.kind == constants.CONS_MESSAGE
2011
    assert self.port or self.kind in [constants.CONS_MESSAGE,
2012
                                      constants.CONS_SSH]
2013
    assert self.user or self.kind in [constants.CONS_MESSAGE,
2014
                                      constants.CONS_SPICE,
2015
                                      constants.CONS_VNC]
2016
    assert self.command or self.kind in [constants.CONS_MESSAGE,
2017
                                         constants.CONS_SPICE,
2018
                                         constants.CONS_VNC]
2019
    assert self.display or self.kind in [constants.CONS_MESSAGE,
2020
                                         constants.CONS_SPICE,
2021
                                         constants.CONS_SSH]
2022
    return True
2023

    
2024

    
2025
class Network(ConfigObject):
2026
  """Object representing a network definition for ganeti.
2027

2028
  """
2029
  __slots__ = [
2030
    "name",
2031
    "serial_no",
2032
    "network_type",
2033
    "mac_prefix",
2034
    "family",
2035
    "network",
2036
    "network6",
2037
    "gateway",
2038
    "gateway6",
2039
    "size",
2040
    "reservations",
2041
    "ext_reservations",
2042
    ] + _TIMESTAMPS + _UUID
2043

    
2044

    
2045
class SerializableConfigParser(ConfigParser.SafeConfigParser):
2046
  """Simple wrapper over ConfigParse that allows serialization.
2047

2048
  This class is basically ConfigParser.SafeConfigParser with two
2049
  additional methods that allow it to serialize/unserialize to/from a
2050
  buffer.
2051

2052
  """
2053
  def Dumps(self):
2054
    """Dump this instance and return the string representation."""
2055
    buf = StringIO()
2056
    self.write(buf)
2057
    return buf.getvalue()
2058

    
2059
  @classmethod
2060
  def Loads(cls, data):
2061
    """Load data from a string."""
2062
    buf = StringIO(data)
2063
    cfp = cls()
2064
    cfp.readfp(buf)
2065
    return cfp