Statistics
| Branch: | Tag: | Revision:

root / lib / objects.py @ 32683096

History | View | Annotate | Download (57.8 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 explicitly 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 objectutils
48
from ganeti import utils
49

    
50
from socket import AF_INET
51

    
52

    
53
__all__ = ["ConfigObject", "ConfigData", "NIC", "Disk", "Instance",
54
           "OS", "Node", "NodeGroup", "Cluster", "FillDict"]
55

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

    
59

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

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

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

    
83

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

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

    
101
  return ret_dict
102

    
103

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

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

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

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

    
116

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

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

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

    
133

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

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

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

    
147

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

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

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

    
162
  return result
163

    
164

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

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

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

    
177
  if (constants.ND_OOB_PROGRAM in ndparams and
178
      ndparams[constants.ND_OOB_PROGRAM] is None):
179
    # will be reset by the line below
180
    del ndparams[constants.ND_OOB_PROGRAM]
181
  return FillDict(constants.NDC_DEFAULTS, ndparams)
182

    
183

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

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

    
194

    
195
class ConfigObject(objectutils.ValidatedSlots):
196
  """A generic config object.
197

198
  It has the following properties:
199

200
    - provides somewhat safe recursive unpickling and pickling for its classes
201
    - unset attributes which are defined in slots are always returned
202
      as None instead of raising an error
203

204
  Classes derived from this must always declare __slots__ (we use many
205
  config objects and the memory reduction is useful)
206

207
  """
208
  __slots__ = []
209

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

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

    
222
  def Validate(self):
223
    """Validates the slots.
224

225
    """
226

    
227
  def ToDict(self):
228
    """Convert to a dict holding only standard python types.
229

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

236
    """
237
    result = {}
238
    for name in self.GetAllSlots():
239
      value = getattr(self, name, None)
240
      if value is not None:
241
        result[name] = value
242
    return result
243

    
244
  __getstate__ = ToDict
245

    
246
  @classmethod
247
  def FromDict(cls, val):
248
    """Create an object from a dictionary.
249

250
    This generic routine takes a dict, instantiates a new instance of
251
    the given class, and sets attributes based on the dict content.
252

253
    As for `ToDict`, this does not work if the class has children
254
    who are ConfigObjects themselves (e.g. the nics list in an
255
    Instance), in which case the object should subclass the function
256
    and alter the objects.
257

258
    """
259
    if not isinstance(val, dict):
260
      raise errors.ConfigurationError("Invalid object passed to FromDict:"
261
                                      " expected dict, got %s" % type(val))
262
    val_str = dict([(str(k), v) for k, v in val.iteritems()])
263
    obj = cls(**val_str) # pylint: disable=W0142
264
    return obj
265

    
266
  @staticmethod
267
  def _ContainerToDicts(container):
268
    """Convert the elements of a container to standard python types.
269

270
    This method converts a container with elements derived from
271
    ConfigData to standard python types. If the container is a dict,
272
    we don't touch the keys, only the values.
273

274
    """
275
    if isinstance(container, dict):
276
      ret = dict([(k, v.ToDict()) for k, v in container.iteritems()])
277
    elif isinstance(container, (list, tuple, set, frozenset)):
278
      ret = [elem.ToDict() for elem in container]
279
    else:
280
      raise TypeError("Invalid type %s passed to _ContainerToDicts" %
281
                      type(container))
282
    return ret
283

    
284
  @staticmethod
285
  def _ContainerFromDicts(source, c_type, e_type):
286
    """Convert a container from standard python types.
287

288
    This method converts a container with standard python types to
289
    ConfigData objects. If the container is a dict, we don't touch the
290
    keys, only the values.
291

292
    """
293
    if not isinstance(c_type, type):
294
      raise TypeError("Container type %s passed to _ContainerFromDicts is"
295
                      " not a type" % type(c_type))
296
    if source is None:
297
      source = c_type()
298
    if c_type is dict:
299
      ret = dict([(k, e_type.FromDict(v)) for k, v in source.iteritems()])
300
    elif c_type in (list, tuple, set, frozenset):
301
      ret = c_type([e_type.FromDict(elem) for elem in source])
302
    else:
303
      raise TypeError("Invalid container type %s passed to"
304
                      " _ContainerFromDicts" % c_type)
305
    return ret
306

    
307
  def Copy(self):
308
    """Makes a deep copy of the current object and its children.
309

310
    """
311
    dict_form = self.ToDict()
312
    clone_obj = self.__class__.FromDict(dict_form)
313
    return clone_obj
314

    
315
  def __repr__(self):
316
    """Implement __repr__ for ConfigObjects."""
317
    return repr(self.ToDict())
318

    
319
  def UpgradeConfig(self):
320
    """Fill defaults for missing configuration values.
321

322
    This method will be called at configuration load time, and its
323
    implementation will be object dependent.
324

325
    """
326
    pass
327

    
328

    
329
class TaggableObject(ConfigObject):
330
  """An generic class supporting tags.
331

332
  """
333
  __slots__ = ["tags"]
334
  VALID_TAG_RE = re.compile("^[\w.+*/:@-]+$")
335

    
336
  @classmethod
337
  def ValidateTag(cls, tag):
338
    """Check if a tag is valid.
339

340
    If the tag is invalid, an errors.TagError will be raised. The
341
    function has no return value.
342

343
    """
344
    if not isinstance(tag, basestring):
345
      raise errors.TagError("Invalid tag type (not a string)")
346
    if len(tag) > constants.MAX_TAG_LEN:
347
      raise errors.TagError("Tag too long (>%d characters)" %
348
                            constants.MAX_TAG_LEN)
349
    if not tag:
350
      raise errors.TagError("Tags cannot be empty")
351
    if not cls.VALID_TAG_RE.match(tag):
352
      raise errors.TagError("Tag contains invalid characters")
353

    
354
  def GetTags(self):
355
    """Return the tags list.
356

357
    """
358
    tags = getattr(self, "tags", None)
359
    if tags is None:
360
      tags = self.tags = set()
361
    return tags
362

    
363
  def AddTag(self, tag):
364
    """Add a new tag.
365

366
    """
367
    self.ValidateTag(tag)
368
    tags = self.GetTags()
369
    if len(tags) >= constants.MAX_TAGS_PER_OBJ:
370
      raise errors.TagError("Too many tags")
371
    self.GetTags().add(tag)
372

    
373
  def RemoveTag(self, tag):
374
    """Remove a tag.
375

376
    """
377
    self.ValidateTag(tag)
378
    tags = self.GetTags()
379
    try:
380
      tags.remove(tag)
381
    except KeyError:
382
      raise errors.TagError("Tag not found")
383

    
384
  def ToDict(self):
385
    """Taggable-object-specific conversion to standard python types.
386

387
    This replaces the tags set with a list.
388

389
    """
390
    bo = super(TaggableObject, self).ToDict()
391

    
392
    tags = bo.get("tags", None)
393
    if isinstance(tags, set):
394
      bo["tags"] = list(tags)
395
    return bo
396

    
397
  @classmethod
398
  def FromDict(cls, val):
399
    """Custom function for instances.
400

401
    """
402
    obj = super(TaggableObject, cls).FromDict(val)
403
    if hasattr(obj, "tags") and isinstance(obj.tags, list):
404
      obj.tags = set(obj.tags)
405
    return obj
406

    
407

    
408
class MasterNetworkParameters(ConfigObject):
409
  """Network configuration parameters for the master
410

411
  @ivar name: master name
412
  @ivar ip: master IP
413
  @ivar netmask: master netmask
414
  @ivar netdev: master network device
415
  @ivar ip_family: master IP family
416

417
  """
418
  __slots__ = [
419
    "name",
420
    "ip",
421
    "netmask",
422
    "netdev",
423
    "ip_family"
424
    ]
425

    
426

    
427
class ConfigData(ConfigObject):
428
  """Top-level config object."""
429
  __slots__ = [
430
    "version",
431
    "cluster",
432
    "nodes",
433
    "nodegroups",
434
    "instances",
435
    "serial_no",
436
    ] + _TIMESTAMPS
437

    
438
  def ToDict(self):
439
    """Custom function for top-level config data.
440

441
    This just replaces the list of instances, nodes and the cluster
442
    with standard python types.
443

444
    """
445
    mydict = super(ConfigData, self).ToDict()
446
    mydict["cluster"] = mydict["cluster"].ToDict()
447
    for key in "nodes", "instances", "nodegroups":
448
      mydict[key] = self._ContainerToDicts(mydict[key])
449

    
450
    return mydict
451

    
452
  @classmethod
453
  def FromDict(cls, val):
454
    """Custom function for top-level config data
455

456
    """
457
    obj = super(ConfigData, cls).FromDict(val)
458
    obj.cluster = Cluster.FromDict(obj.cluster)
459
    obj.nodes = cls._ContainerFromDicts(obj.nodes, dict, Node)
460
    obj.instances = cls._ContainerFromDicts(obj.instances, dict, Instance)
461
    obj.nodegroups = cls._ContainerFromDicts(obj.nodegroups, dict, NodeGroup)
462
    return obj
463

    
464
  def HasAnyDiskOfType(self, dev_type):
465
    """Check if in there is at disk of the given type in the configuration.
466

467
    @type dev_type: L{constants.LDS_BLOCK}
468
    @param dev_type: the type to look for
469
    @rtype: boolean
470
    @return: boolean indicating if a disk of the given type was found or not
471

472
    """
473
    for instance in self.instances.values():
474
      for disk in instance.disks:
475
        if disk.IsBasedOnDiskType(dev_type):
476
          return True
477
    return False
478

    
479
  def UpgradeConfig(self):
480
    """Fill defaults for missing configuration values.
481

482
    """
483
    self.cluster.UpgradeConfig()
484
    for node in self.nodes.values():
485
      node.UpgradeConfig()
486
    for instance in self.instances.values():
487
      instance.UpgradeConfig()
488
    if self.nodegroups is None:
489
      self.nodegroups = {}
490
    for nodegroup in self.nodegroups.values():
491
      nodegroup.UpgradeConfig()
492
    if self.cluster.drbd_usermode_helper is None:
493
      # To decide if we set an helper let's check if at least one instance has
494
      # a DRBD disk. This does not cover all the possible scenarios but it
495
      # gives a good approximation.
496
      if self.HasAnyDiskOfType(constants.LD_DRBD8):
497
        self.cluster.drbd_usermode_helper = constants.DEFAULT_DRBD_HELPER
498

    
499

    
500
class NIC(ConfigObject):
501
  """Config object representing a network card."""
502
  __slots__ = ["mac", "ip", "nicparams"]
503

    
504
  @classmethod
505
  def CheckParameterSyntax(cls, nicparams):
506
    """Check the given parameters for validity.
507

508
    @type nicparams:  dict
509
    @param nicparams: dictionary with parameter names/value
510
    @raise errors.ConfigurationError: when a parameter is not valid
511

512
    """
513
    if (nicparams[constants.NIC_MODE] not in constants.NIC_VALID_MODES and
514
        nicparams[constants.NIC_MODE] != constants.VALUE_AUTO):
515
      err = "Invalid nic mode: %s" % nicparams[constants.NIC_MODE]
516
      raise errors.ConfigurationError(err)
517

    
518
    if (nicparams[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED and
519
        not nicparams[constants.NIC_LINK]):
520
      err = "Missing bridged nic link"
521
      raise errors.ConfigurationError(err)
522

    
523

    
524
class Disk(ConfigObject):
525
  """Config object representing a block device."""
526
  __slots__ = ["dev_type", "logical_id", "physical_id",
527
               "children", "iv_name", "size", "mode", "params"]
528

    
529
  def CreateOnSecondary(self):
530
    """Test if this device needs to be created on a secondary node."""
531
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
532

    
533
  def AssembleOnSecondary(self):
534
    """Test if this device needs to be assembled on a secondary node."""
535
    return self.dev_type in (constants.LD_DRBD8, constants.LD_LV)
536

    
537
  def OpenOnSecondary(self):
538
    """Test if this device needs to be opened on a secondary node."""
539
    return self.dev_type in (constants.LD_LV,)
540

    
541
  def StaticDevPath(self):
542
    """Return the device path if this device type has a static one.
543

544
    Some devices (LVM for example) live always at the same /dev/ path,
545
    irrespective of their status. For such devices, we return this
546
    path, for others we return None.
547

548
    @warning: The path returned is not a normalized pathname; callers
549
        should check that it is a valid path.
550

551
    """
552
    if self.dev_type == constants.LD_LV:
553
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
554
    elif self.dev_type == constants.LD_BLOCKDEV:
555
      return self.logical_id[1]
556
    elif self.dev_type == constants.LD_RBD:
557
      return "/dev/%s/%s" % (self.logical_id[0], self.logical_id[1])
558
    return None
559

    
560
  def ChildrenNeeded(self):
561
    """Compute the needed number of children for activation.
562

563
    This method will return either -1 (all children) or a positive
564
    number denoting the minimum number of children needed for
565
    activation (only mirrored devices will usually return >=0).
566

567
    Currently, only DRBD8 supports diskless activation (therefore we
568
    return 0), for all other we keep the previous semantics and return
569
    -1.
570

571
    """
572
    if self.dev_type == constants.LD_DRBD8:
573
      return 0
574
    return -1
575

    
576
  def IsBasedOnDiskType(self, dev_type):
577
    """Check if the disk or its children are based on the given type.
578

579
    @type dev_type: L{constants.LDS_BLOCK}
580
    @param dev_type: the type to look for
581
    @rtype: boolean
582
    @return: boolean indicating if a device of the given type was found or not
583

584
    """
585
    if self.children:
586
      for child in self.children:
587
        if child.IsBasedOnDiskType(dev_type):
588
          return True
589
    return self.dev_type == dev_type
590

    
591
  def GetNodes(self, node):
592
    """This function returns the nodes this device lives on.
593

594
    Given the node on which the parent of the device lives on (or, in
595
    case of a top-level device, the primary node of the devices'
596
    instance), this function will return a list of nodes on which this
597
    devices needs to (or can) be assembled.
598

599
    """
600
    if self.dev_type in [constants.LD_LV, constants.LD_FILE,
601
                         constants.LD_BLOCKDEV, constants.LD_RBD]:
602
      result = [node]
603
    elif self.dev_type in constants.LDS_DRBD:
604
      result = [self.logical_id[0], self.logical_id[1]]
605
      if node not in result:
606
        raise errors.ConfigurationError("DRBD device passed unknown node")
607
    else:
608
      raise errors.ProgrammerError("Unhandled device type %s" % self.dev_type)
609
    return result
610

    
611
  def ComputeNodeTree(self, parent_node):
612
    """Compute the node/disk tree for this disk and its children.
613

614
    This method, given the node on which the parent disk lives, will
615
    return the list of all (node, disk) pairs which describe the disk
616
    tree in the most compact way. For example, a drbd/lvm stack
617
    will be returned as (primary_node, drbd) and (secondary_node, drbd)
618
    which represents all the top-level devices on the nodes.
619

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

    
646
  def ComputeGrowth(self, amount):
647
    """Compute the per-VG growth requirements.
648

649
    This only works for VG-based disks.
650

651
    @type amount: integer
652
    @param amount: the desired increase in (user-visible) disk space
653
    @rtype: dict
654
    @return: a dictionary of volume-groups and the required size
655

656
    """
657
    if self.dev_type == constants.LD_LV:
658
      return {self.logical_id[0]: amount}
659
    elif self.dev_type == constants.LD_DRBD8:
660
      if self.children:
661
        return self.children[0].ComputeGrowth(amount)
662
      else:
663
        return {}
664
    else:
665
      # Other disk types do not require VG space
666
      return {}
667

    
668
  def RecordGrow(self, amount):
669
    """Update the size of this disk after growth.
670

671
    This method recurses over the disks's children and updates their
672
    size correspondigly. The method needs to be kept in sync with the
673
    actual algorithms from bdev.
674

675
    """
676
    if self.dev_type in (constants.LD_LV, constants.LD_FILE,
677
                         constants.LD_RBD):
678
      self.size += amount
679
    elif self.dev_type == constants.LD_DRBD8:
680
      if self.children:
681
        self.children[0].RecordGrow(amount)
682
      self.size += amount
683
    else:
684
      raise errors.ProgrammerError("Disk.RecordGrow called for unsupported"
685
                                   " disk type %s" % self.dev_type)
686

    
687
  def Update(self, size=None, mode=None):
688
    """Apply changes to size and mode.
689

690
    """
691
    if self.dev_type == constants.LD_DRBD8:
692
      if self.children:
693
        self.children[0].Update(size=size, mode=mode)
694
    else:
695
      assert not self.children
696

    
697
    if size is not None:
698
      self.size = size
699
    if mode is not None:
700
      self.mode = mode
701

    
702
  def UnsetSize(self):
703
    """Sets recursively the size to zero for the disk and its children.
704

705
    """
706
    if self.children:
707
      for child in self.children:
708
        child.UnsetSize()
709
    self.size = 0
710

    
711
  def SetPhysicalID(self, target_node, nodes_ip):
712
    """Convert the logical ID to the physical ID.
713

714
    This is used only for drbd, which needs ip/port configuration.
715

716
    The routine descends down and updates its children also, because
717
    this helps when the only the top device is passed to the remote
718
    node.
719

720
    Arguments:
721
      - target_node: the node we wish to configure for
722
      - nodes_ip: a mapping of node name to ip
723

724
    The target_node must exist in in nodes_ip, and must be one of the
725
    nodes in the logical ID for each of the DRBD devices encountered
726
    in the disk tree.
727

728
    """
729
    if self.children:
730
      for child in self.children:
731
        child.SetPhysicalID(target_node, nodes_ip)
732

    
733
    if self.logical_id is None and self.physical_id is not None:
734
      return
735
    if self.dev_type in constants.LDS_DRBD:
736
      pnode, snode, port, pminor, sminor, secret = self.logical_id
737
      if target_node not in (pnode, snode):
738
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
739
                                        target_node)
740
      pnode_ip = nodes_ip.get(pnode, None)
741
      snode_ip = nodes_ip.get(snode, None)
742
      if pnode_ip is None or snode_ip is None:
743
        raise errors.ConfigurationError("Can't find primary or secondary node"
744
                                        " for %s" % str(self))
745
      p_data = (pnode_ip, port)
746
      s_data = (snode_ip, port)
747
      if pnode == target_node:
748
        self.physical_id = p_data + s_data + (pminor, secret)
749
      else: # it must be secondary, we tested above
750
        self.physical_id = s_data + p_data + (sminor, secret)
751
    else:
752
      self.physical_id = self.logical_id
753
    return
754

    
755
  def ToDict(self):
756
    """Disk-specific conversion to standard python types.
757

758
    This replaces the children lists of objects with lists of
759
    standard python types.
760

761
    """
762
    bo = super(Disk, self).ToDict()
763

    
764
    for attr in ("children",):
765
      alist = bo.get(attr, None)
766
      if alist:
767
        bo[attr] = self._ContainerToDicts(alist)
768
    return bo
769

    
770
  @classmethod
771
  def FromDict(cls, val):
772
    """Custom function for Disks
773

774
    """
775
    obj = super(Disk, cls).FromDict(val)
776
    if obj.children:
777
      obj.children = cls._ContainerFromDicts(obj.children, list, Disk)
778
    if obj.logical_id and isinstance(obj.logical_id, list):
779
      obj.logical_id = tuple(obj.logical_id)
780
    if obj.physical_id and isinstance(obj.physical_id, list):
781
      obj.physical_id = tuple(obj.physical_id)
782
    if obj.dev_type in constants.LDS_DRBD:
783
      # we need a tuple of length six here
784
      if len(obj.logical_id) < 6:
785
        obj.logical_id += (None,) * (6 - len(obj.logical_id))
786
    return obj
787

    
788
  def __str__(self):
789
    """Custom str() formatter for disks.
790

791
    """
792
    if self.dev_type == constants.LD_LV:
793
      val = "<LogicalVolume(/dev/%s/%s" % self.logical_id
794
    elif self.dev_type in constants.LDS_DRBD:
795
      node_a, node_b, port, minor_a, minor_b = self.logical_id[:5]
796
      val = "<DRBD8("
797
      if self.physical_id is None:
798
        phy = "unconfigured"
799
      else:
800
        phy = ("configured as %s:%s %s:%s" %
801
               (self.physical_id[0], self.physical_id[1],
802
                self.physical_id[2], self.physical_id[3]))
803

    
804
      val += ("hosts=%s/%d-%s/%d, port=%s, %s, " %
805
              (node_a, minor_a, node_b, minor_b, port, phy))
806
      if self.children and self.children.count(None) == 0:
807
        val += "backend=%s, metadev=%s" % (self.children[0], self.children[1])
808
      else:
809
        val += "no local storage"
810
    else:
811
      val = ("<Disk(type=%s, logical_id=%s, physical_id=%s, children=%s" %
812
             (self.dev_type, self.logical_id, self.physical_id, self.children))
813
    if self.iv_name is None:
814
      val += ", not visible"
815
    else:
816
      val += ", visible as /dev/%s" % self.iv_name
817
    if isinstance(self.size, int):
818
      val += ", size=%dm)>" % self.size
819
    else:
820
      val += ", size='%s')>" % (self.size,)
821
    return val
822

    
823
  def Verify(self):
824
    """Checks that this disk is correctly configured.
825

826
    """
827
    all_errors = []
828
    if self.mode not in constants.DISK_ACCESS_SET:
829
      all_errors.append("Disk access mode '%s' is invalid" % (self.mode, ))
830
    return all_errors
831

    
832
  def UpgradeConfig(self):
833
    """Fill defaults for missing configuration values.
834

835
    """
836
    if self.children:
837
      for child in self.children:
838
        child.UpgradeConfig()
839

    
840
    # FIXME: Make this configurable in Ganeti 2.7
841
    self.params = {}
842
    # add here config upgrade for this disk
843

    
844
  @staticmethod
845
  def ComputeLDParams(disk_template, disk_params):
846
    """Computes Logical Disk parameters from Disk Template parameters.
847

848
    @type disk_template: string
849
    @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
850
    @type disk_params: dict
851
    @param disk_params: disk template parameters;
852
                        dict(template_name -> parameters
853
    @rtype: list(dict)
854
    @return: a list of dicts, one for each node of the disk hierarchy. Each dict
855
      contains the LD parameters of the node. The tree is flattened in-order.
856

857
    """
858
    if disk_template not in constants.DISK_TEMPLATES:
859
      raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
860

    
861
    assert disk_template in disk_params
862

    
863
    result = list()
864
    dt_params = disk_params[disk_template]
865
    if disk_template == constants.DT_DRBD8:
866
      drbd_params = {
867
        constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
868
        constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
869
        constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
870
        constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
871
        constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
872
        constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
873
        constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
874
        constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
875
        constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
876
        constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
877
        constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
878
        constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
879
        }
880

    
881
      drbd_params = \
882
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8],
883
                 drbd_params)
884

    
885
      result.append(drbd_params)
886

    
887
      # data LV
888
      data_params = {
889
        constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
890
        }
891
      data_params = \
892
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
893
                 data_params)
894
      result.append(data_params)
895

    
896
      # metadata LV
897
      meta_params = {
898
        constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
899
        }
900
      meta_params = \
901
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
902
                 meta_params)
903
      result.append(meta_params)
904

    
905
    elif (disk_template == constants.DT_FILE or
906
          disk_template == constants.DT_SHARED_FILE):
907
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
908

    
909
    elif disk_template == constants.DT_PLAIN:
910
      params = {
911
        constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
912
        }
913
      params = \
914
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
915
                 params)
916
      result.append(params)
917

    
918
    elif disk_template == constants.DT_BLOCK:
919
      result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
920

    
921
    elif disk_template == constants.DT_RBD:
922
      params = {
923
        constants.LDP_POOL: dt_params[constants.RBD_POOL]
924
        }
925
      params = \
926
        FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD],
927
                 params)
928
      result.append(params)
929

    
930
    return result
931

    
932

    
933
class InstancePolicy(ConfigObject):
934
  """Config object representing instance policy limits dictionary.
935

936

937
  Note that this object is not actually used in the config, it's just
938
  used as a placeholder for a few functions.
939

940
  """
941
  @classmethod
942
  def CheckParameterSyntax(cls, ipolicy, check_std):
943
    """ Check the instance policy for validity.
944

945
    """
946
    for param in constants.ISPECS_PARAMETERS:
947
      InstancePolicy.CheckISpecSyntax(ipolicy, param, check_std)
948
    if constants.IPOLICY_DTS in ipolicy:
949
      InstancePolicy.CheckDiskTemplates(ipolicy[constants.IPOLICY_DTS])
950
    for key in constants.IPOLICY_PARAMETERS:
951
      if key in ipolicy:
952
        InstancePolicy.CheckParameter(key, ipolicy[key])
953
    wrong_keys = frozenset(ipolicy.keys()) - constants.IPOLICY_ALL_KEYS
954
    if wrong_keys:
955
      raise errors.ConfigurationError("Invalid keys in ipolicy: %s" %
956
                                      utils.CommaJoin(wrong_keys))
957

    
958
  @classmethod
959
  def CheckISpecSyntax(cls, ipolicy, name, check_std):
960
    """Check the instance policy for validity on a given key.
961

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

965
    @type ipolicy: dict
966
    @param ipolicy: dictionary with min, max, std specs
967
    @type name: string
968
    @param name: what are the limits for
969
    @type check_std: bool
970
    @param check_std: Whether to check std value or just assume compliance
971
    @raise errors.ConfigureError: when specs for given name are not valid
972

973
    """
974
    min_v = ipolicy[constants.ISPECS_MIN].get(name, 0)
975

    
976
    if check_std:
977
      std_v = ipolicy[constants.ISPECS_STD].get(name, min_v)
978
      std_msg = std_v
979
    else:
980
      std_v = min_v
981
      std_msg = "-"
982

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

    
992
  @classmethod
993
  def CheckDiskTemplates(cls, disk_templates):
994
    """Checks the disk templates for validity.
995

996
    """
997
    wrong = frozenset(disk_templates).difference(constants.DISK_TEMPLATES)
998
    if wrong:
999
      raise errors.ConfigurationError("Invalid disk template(s) %s" %
1000
                                      utils.CommaJoin(wrong))
1001

    
1002
  @classmethod
1003
  def CheckParameter(cls, key, value):
1004
    """Checks a parameter.
1005

1006
    Currently we expect all parameters to be float values.
1007

1008
    """
1009
    try:
1010
      float(value)
1011
    except (TypeError, ValueError), err:
1012
      raise errors.ConfigurationError("Invalid value for key" " '%s':"
1013
                                      " '%s', error: %s" % (key, value, err))
1014

    
1015

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

    
1034
  def _ComputeSecondaryNodes(self):
1035
    """Compute the list of secondary nodes.
1036

1037
    This is a simple wrapper over _ComputeAllNodes.
1038

1039
    """
1040
    all_nodes = set(self._ComputeAllNodes())
1041
    all_nodes.discard(self.primary_node)
1042
    return tuple(all_nodes)
1043

    
1044
  secondary_nodes = property(_ComputeSecondaryNodes, None, None,
1045
                             "List of secondary nodes")
1046

    
1047
  def _ComputeAllNodes(self):
1048
    """Compute the list of all nodes.
1049

1050
    Since the data is already there (in the drbd disks), keeping it as
1051
    a separate normal attribute is redundant and if not properly
1052
    synchronised can cause problems. Thus it's better to compute it
1053
    dynamically.
1054

1055
    """
1056
    def _Helper(nodes, device):
1057
      """Recursively computes nodes given a top device."""
1058
      if device.dev_type in constants.LDS_DRBD:
1059
        nodea, nodeb = device.logical_id[:2]
1060
        nodes.add(nodea)
1061
        nodes.add(nodeb)
1062
      if device.children:
1063
        for child in device.children:
1064
          _Helper(nodes, child)
1065

    
1066
    all_nodes = set()
1067
    all_nodes.add(self.primary_node)
1068
    for device in self.disks:
1069
      _Helper(all_nodes, device)
1070
    return tuple(all_nodes)
1071

    
1072
  all_nodes = property(_ComputeAllNodes, None, None,
1073
                       "List of all nodes of the instance")
1074

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

1078
    This function figures out what logical volumes should belong on
1079
    which nodes, recursing through a device tree.
1080

1081
    @param lvmap: optional dictionary to receive the
1082
        'node' : ['lv', ...] data.
1083

1084
    @return: None if lvmap arg is given, otherwise, a dictionary of
1085
        the form { 'nodename' : ['volume1', 'volume2', ...], ... };
1086
        volumeN is of the form "vg_name/lv_name", compatible with
1087
        GetVolumeList()
1088

1089
    """
1090
    if node is None:
1091
      node = self.primary_node
1092

    
1093
    if lvmap is None:
1094
      lvmap = {
1095
        node: [],
1096
        }
1097
      ret = lvmap
1098
    else:
1099
      if not node in lvmap:
1100
        lvmap[node] = []
1101
      ret = None
1102

    
1103
    if not devs:
1104
      devs = self.disks
1105

    
1106
    for dev in devs:
1107
      if dev.dev_type == constants.LD_LV:
1108
        lvmap[node].append(dev.logical_id[0] + "/" + dev.logical_id[1])
1109

    
1110
      elif dev.dev_type in constants.LDS_DRBD:
1111
        if dev.children:
1112
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[0])
1113
          self.MapLVsByNode(lvmap, dev.children, dev.logical_id[1])
1114

    
1115
      elif dev.children:
1116
        self.MapLVsByNode(lvmap, dev.children, node)
1117

    
1118
    return ret
1119

    
1120
  def FindDisk(self, idx):
1121
    """Find a disk given having a specified index.
1122

1123
    This is just a wrapper that does validation of the index.
1124

1125
    @type idx: int
1126
    @param idx: the disk index
1127
    @rtype: L{Disk}
1128
    @return: the corresponding disk
1129
    @raise errors.OpPrereqError: when the given index is not valid
1130

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

    
1143
  def ToDict(self):
1144
    """Instance-specific conversion to standard python types.
1145

1146
    This replaces the children lists of objects with lists of standard
1147
    python types.
1148

1149
    """
1150
    bo = super(Instance, self).ToDict()
1151

    
1152
    for attr in "nics", "disks":
1153
      alist = bo.get(attr, None)
1154
      if alist:
1155
        nlist = self._ContainerToDicts(alist)
1156
      else:
1157
        nlist = []
1158
      bo[attr] = nlist
1159
    return bo
1160

    
1161
  @classmethod
1162
  def FromDict(cls, val):
1163
    """Custom function for instances.
1164

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

    
1178
  def UpgradeConfig(self):
1179
    """Fill defaults for missing configuration values.
1180

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

    
1196

    
1197
class OS(ConfigObject):
1198
  """Config object representing an operating system.
1199

1200
  @type supported_parameters: list
1201
  @ivar supported_parameters: a list of tuples, name and description,
1202
      containing the supported parameters by this OS
1203

1204
  @type VARIANT_DELIM: string
1205
  @cvar VARIANT_DELIM: the variant delimiter
1206

1207
  """
1208
  __slots__ = [
1209
    "name",
1210
    "path",
1211
    "api_versions",
1212
    "create_script",
1213
    "export_script",
1214
    "import_script",
1215
    "rename_script",
1216
    "verify_script",
1217
    "supported_variants",
1218
    "supported_parameters",
1219
    ]
1220

    
1221
  VARIANT_DELIM = "+"
1222

    
1223
  @classmethod
1224
  def SplitNameVariant(cls, name):
1225
    """Splits the name into the proper name and variant.
1226

1227
    @param name: the OS (unprocessed) name
1228
    @rtype: list
1229
    @return: a list of two elements; if the original name didn't
1230
        contain a variant, it's returned as an empty string
1231

1232
    """
1233
    nv = name.split(cls.VARIANT_DELIM, 1)
1234
    if len(nv) == 1:
1235
      nv.append("")
1236
    return nv
1237

    
1238
  @classmethod
1239
  def GetName(cls, name):
1240
    """Returns the proper name of the os (without the variant).
1241

1242
    @param name: the OS (unprocessed) name
1243

1244
    """
1245
    return cls.SplitNameVariant(name)[0]
1246

    
1247
  @classmethod
1248
  def GetVariant(cls, name):
1249
    """Returns the variant the os (without the base name).
1250

1251
    @param name: the OS (unprocessed) name
1252

1253
    """
1254
    return cls.SplitNameVariant(name)[1]
1255

    
1256

    
1257
class NodeHvState(ConfigObject):
1258
  """Hypvervisor state on a node.
1259

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

1269
  """
1270
  __slots__ = [
1271
    "mem_total",
1272
    "mem_node",
1273
    "mem_hv",
1274
    "mem_inst",
1275
    "cpu_total",
1276
    "cpu_node",
1277
    ] + _TIMESTAMPS
1278

    
1279

    
1280
class NodeDiskState(ConfigObject):
1281
  """Disk state on a node.
1282

1283
  """
1284
  __slots__ = [
1285
    "total",
1286
    "reserved",
1287
    "overhead",
1288
    ] + _TIMESTAMPS
1289

    
1290

    
1291
class Node(TaggableObject):
1292
  """Config object representing a node.
1293

1294
  @ivar hv_state: Hypervisor state (e.g. number of CPUs)
1295
  @ivar hv_state_static: Hypervisor state overriden by user
1296
  @ivar disk_state: Disk state (e.g. free space)
1297
  @ivar disk_state_static: Disk state overriden by user
1298

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

    
1319
  def UpgradeConfig(self):
1320
    """Fill defaults for missing configuration values.
1321

1322
    """
1323
    # pylint: disable=E0203
1324
    # because these are "defined" via slots, not manually
1325
    if self.master_capable is None:
1326
      self.master_capable = True
1327

    
1328
    if self.vm_capable is None:
1329
      self.vm_capable = True
1330

    
1331
    if self.ndparams is None:
1332
      self.ndparams = {}
1333

    
1334
    if self.powered is None:
1335
      self.powered = True
1336

    
1337
  def ToDict(self):
1338
    """Custom function for serializing.
1339

1340
    """
1341
    data = super(Node, self).ToDict()
1342

    
1343
    hv_state = data.get("hv_state", None)
1344
    if hv_state is not None:
1345
      data["hv_state"] = self._ContainerToDicts(hv_state)
1346

    
1347
    disk_state = data.get("disk_state", None)
1348
    if disk_state is not None:
1349
      data["disk_state"] = \
1350
        dict((key, self._ContainerToDicts(value))
1351
             for (key, value) in disk_state.items())
1352

    
1353
    return data
1354

    
1355
  @classmethod
1356
  def FromDict(cls, val):
1357
    """Custom function for deserializing.
1358

1359
    """
1360
    obj = super(Node, cls).FromDict(val)
1361

    
1362
    if obj.hv_state is not None:
1363
      obj.hv_state = cls._ContainerFromDicts(obj.hv_state, dict, NodeHvState)
1364

    
1365
    if obj.disk_state is not None:
1366
      obj.disk_state = \
1367
        dict((key, cls._ContainerFromDicts(value, dict, NodeDiskState))
1368
             for (key, value) in obj.disk_state.items())
1369

    
1370
    return obj
1371

    
1372

    
1373
class NodeGroup(TaggableObject):
1374
  """Config object representing a node group."""
1375
  __slots__ = [
1376
    "name",
1377
    "members",
1378
    "ndparams",
1379
    "diskparams",
1380
    "ipolicy",
1381
    "serial_no",
1382
    "hv_state_static",
1383
    "disk_state_static",
1384
    "alloc_policy",
1385
    ] + _TIMESTAMPS + _UUID
1386

    
1387
  def ToDict(self):
1388
    """Custom function for nodegroup.
1389

1390
    This discards the members object, which gets recalculated and is only kept
1391
    in memory.
1392

1393
    """
1394
    mydict = super(NodeGroup, self).ToDict()
1395
    del mydict["members"]
1396
    return mydict
1397

    
1398
  @classmethod
1399
  def FromDict(cls, val):
1400
    """Custom function for nodegroup.
1401

1402
    The members slot is initialized to an empty list, upon deserialization.
1403

1404
    """
1405
    obj = super(NodeGroup, cls).FromDict(val)
1406
    obj.members = []
1407
    return obj
1408

    
1409
  def UpgradeConfig(self):
1410
    """Fill defaults for missing configuration values.
1411

1412
    """
1413
    if self.ndparams is None:
1414
      self.ndparams = {}
1415

    
1416
    if self.serial_no is None:
1417
      self.serial_no = 1
1418

    
1419
    if self.alloc_policy is None:
1420
      self.alloc_policy = constants.ALLOC_POLICY_PREFERRED
1421

    
1422
    # We only update mtime, and not ctime, since we would not be able
1423
    # to provide a correct value for creation time.
1424
    if self.mtime is None:
1425
      self.mtime = time.time()
1426

    
1427
    if self.diskparams is None:
1428
      self.diskparams = {}
1429
    if self.ipolicy is None:
1430
      self.ipolicy = MakeEmptyIPolicy()
1431

    
1432
  def FillND(self, node):
1433
    """Return filled out ndparams for L{objects.Node}
1434

1435
    @type node: L{objects.Node}
1436
    @param node: A Node object to fill
1437
    @return a copy of the node's ndparams with defaults filled
1438

1439
    """
1440
    return self.SimpleFillND(node.ndparams)
1441

    
1442
  def SimpleFillND(self, ndparams):
1443
    """Fill a given ndparams dict with defaults.
1444

1445
    @type ndparams: dict
1446
    @param ndparams: the dict to fill
1447
    @rtype: dict
1448
    @return: a copy of the passed in ndparams with missing keys filled
1449
        from the node group defaults
1450

1451
    """
1452
    return FillDict(self.ndparams, ndparams)
1453

    
1454

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

    
1499
  def UpgradeConfig(self):
1500
    """Fill defaults for missing configuration values.
1501

1502
    """
1503
    # pylint: disable=E0203
1504
    # because these are "defined" via slots, not manually
1505
    if self.hvparams is None:
1506
      self.hvparams = constants.HVC_DEFAULTS
1507
    else:
1508
      for hypervisor in self.hvparams:
1509
        self.hvparams[hypervisor] = FillDict(
1510
            constants.HVC_DEFAULTS[hypervisor], self.hvparams[hypervisor])
1511

    
1512
    if self.os_hvp is None:
1513
      self.os_hvp = {}
1514

    
1515
    # osparams added before 2.2
1516
    if self.osparams is None:
1517
      self.osparams = {}
1518

    
1519
    self.ndparams = UpgradeNDParams(self.ndparams)
1520

    
1521
    self.beparams = UpgradeGroupedParams(self.beparams,
1522
                                         constants.BEC_DEFAULTS)
1523
    for beparams_group in self.beparams:
1524
      UpgradeBeParams(self.beparams[beparams_group])
1525

    
1526
    migrate_default_bridge = not self.nicparams
1527
    self.nicparams = UpgradeGroupedParams(self.nicparams,
1528
                                          constants.NICC_DEFAULTS)
1529
    if migrate_default_bridge:
1530
      self.nicparams[constants.PP_DEFAULT][constants.NIC_LINK] = \
1531
        self.default_bridge
1532

    
1533
    if self.modify_etc_hosts is None:
1534
      self.modify_etc_hosts = True
1535

    
1536
    if self.modify_ssh_setup is None:
1537
      self.modify_ssh_setup = True
1538

    
1539
    # default_bridge is no longer used in 2.1. The slot is left there to
1540
    # support auto-upgrading. It can be removed once we decide to deprecate
1541
    # upgrading straight from 2.0.
1542
    if self.default_bridge is not None:
1543
      self.default_bridge = None
1544

    
1545
    # default_hypervisor is just the first enabled one in 2.1. This slot and
1546
    # code can be removed once upgrading straight from 2.0 is deprecated.
1547
    if self.default_hypervisor is not None:
1548
      self.enabled_hypervisors = ([self.default_hypervisor] +
1549
                                  [hvname for hvname in self.enabled_hypervisors
1550
                                   if hvname != self.default_hypervisor])
1551
      self.default_hypervisor = None
1552

    
1553
    # maintain_node_health added after 2.1.1
1554
    if self.maintain_node_health is None:
1555
      self.maintain_node_health = False
1556

    
1557
    if self.uid_pool is None:
1558
      self.uid_pool = []
1559

    
1560
    if self.default_iallocator is None:
1561
      self.default_iallocator = ""
1562

    
1563
    # reserved_lvs added before 2.2
1564
    if self.reserved_lvs is None:
1565
      self.reserved_lvs = []
1566

    
1567
    # hidden and blacklisted operating systems added before 2.2.1
1568
    if self.hidden_os is None:
1569
      self.hidden_os = []
1570

    
1571
    if self.blacklisted_os is None:
1572
      self.blacklisted_os = []
1573

    
1574
    # primary_ip_family added before 2.3
1575
    if self.primary_ip_family is None:
1576
      self.primary_ip_family = AF_INET
1577

    
1578
    if self.master_netmask is None:
1579
      ipcls = netutils.IPAddress.GetClassFromIpFamily(self.primary_ip_family)
1580
      self.master_netmask = ipcls.iplen
1581

    
1582
    if self.prealloc_wipe_disks is None:
1583
      self.prealloc_wipe_disks = False
1584

    
1585
    # shared_file_storage_dir added before 2.5
1586
    if self.shared_file_storage_dir is None:
1587
      self.shared_file_storage_dir = ""
1588

    
1589
    if self.use_external_mip_script is None:
1590
      self.use_external_mip_script = False
1591

    
1592
    if self.diskparams:
1593
      self.diskparams = UpgradeDiskParams(self.diskparams)
1594
    else:
1595
      self.diskparams = constants.DISK_DT_DEFAULTS.copy()
1596

    
1597
    # instance policy added before 2.6
1598
    if self.ipolicy is None:
1599
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, {})
1600
    else:
1601
      # we can either make sure to upgrade the ipolicy always, or only
1602
      # do it in some corner cases (e.g. missing keys); note that this
1603
      # will break any removal of keys from the ipolicy dict
1604
      self.ipolicy = FillIPolicy(constants.IPOLICY_DEFAULTS, self.ipolicy)
1605

    
1606
  @property
1607
  def primary_hypervisor(self):
1608
    """The first hypervisor is the primary.
1609

1610
    Useful, for example, for L{Node}'s hv/disk state.
1611

1612
    """
1613
    return self.enabled_hypervisors[0]
1614

    
1615
  def ToDict(self):
1616
    """Custom function for cluster.
1617

1618
    """
1619
    mydict = super(Cluster, self).ToDict()
1620
    mydict["tcpudp_port_pool"] = list(self.tcpudp_port_pool)
1621
    return mydict
1622

    
1623
  @classmethod
1624
  def FromDict(cls, val):
1625
    """Custom function for cluster.
1626

1627
    """
1628
    obj = super(Cluster, cls).FromDict(val)
1629
    if not isinstance(obj.tcpudp_port_pool, set):
1630
      obj.tcpudp_port_pool = set(obj.tcpudp_port_pool)
1631
    return obj
1632

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

1636
    @param diskparams: The diskparams
1637
    @return: The defaults dict
1638

1639
    """
1640
    return FillDiskParams(self.diskparams, diskparams)
1641

    
1642
  def GetHVDefaults(self, hypervisor, os_name=None, skip_keys=None):
1643
    """Get the default hypervisor parameters for the cluster.
1644

1645
    @param hypervisor: the hypervisor name
1646
    @param os_name: if specified, we'll also update the defaults for this OS
1647
    @param skip_keys: if passed, list of keys not to use
1648
    @return: the defaults dict
1649

1650
    """
1651
    if skip_keys is None:
1652
      skip_keys = []
1653

    
1654
    fill_stack = [self.hvparams.get(hypervisor, {})]
1655
    if os_name is not None:
1656
      os_hvp = self.os_hvp.get(os_name, {}).get(hypervisor, {})
1657
      fill_stack.append(os_hvp)
1658

    
1659
    ret_dict = {}
1660
    for o_dict in fill_stack:
1661
      ret_dict = FillDict(ret_dict, o_dict, skip_keys=skip_keys)
1662

    
1663
    return ret_dict
1664

    
1665
  def SimpleFillHV(self, hv_name, os_name, hvparams, skip_globals=False):
1666
    """Fill a given hvparams dict with cluster defaults.
1667

1668
    @type hv_name: string
1669
    @param hv_name: the hypervisor to use
1670
    @type os_name: string
1671
    @param os_name: the OS to use for overriding the hypervisor defaults
1672
    @type skip_globals: boolean
1673
    @param skip_globals: if True, the global hypervisor parameters will
1674
        not be filled
1675
    @rtype: dict
1676
    @return: a copy of the given hvparams with missing keys filled from
1677
        the cluster defaults
1678

1679
    """
1680
    if skip_globals:
1681
      skip_keys = constants.HVC_GLOBALS
1682
    else:
1683
      skip_keys = []
1684

    
1685
    def_dict = self.GetHVDefaults(hv_name, os_name, skip_keys=skip_keys)
1686
    return FillDict(def_dict, hvparams, skip_keys=skip_keys)
1687

    
1688
  def FillHV(self, instance, skip_globals=False):
1689
    """Fill an instance's hvparams dict with cluster defaults.
1690

1691
    @type instance: L{objects.Instance}
1692
    @param instance: the instance parameter to fill
1693
    @type skip_globals: boolean
1694
    @param skip_globals: if True, the global hypervisor parameters will
1695
        not be filled
1696
    @rtype: dict
1697
    @return: a copy of the instance's hvparams with missing keys filled from
1698
        the cluster defaults
1699

1700
    """
1701
    return self.SimpleFillHV(instance.hypervisor, instance.os,
1702
                             instance.hvparams, skip_globals)
1703

    
1704
  def SimpleFillBE(self, beparams):
1705
    """Fill a given beparams dict with cluster defaults.
1706

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

1713
    """
1714
    return FillDict(self.beparams.get(constants.PP_DEFAULT, {}), beparams)
1715

    
1716
  def FillBE(self, instance):
1717
    """Fill an instance's beparams dict with cluster defaults.
1718

1719
    @type instance: L{objects.Instance}
1720
    @param instance: the instance parameter to fill
1721
    @rtype: dict
1722
    @return: a copy of the instance's beparams with missing keys filled from
1723
        the cluster defaults
1724

1725
    """
1726
    return self.SimpleFillBE(instance.beparams)
1727

    
1728
  def SimpleFillNIC(self, nicparams):
1729
    """Fill a given nicparams dict with cluster defaults.
1730

1731
    @type nicparams: dict
1732
    @param nicparams: the dict to fill
1733
    @rtype: dict
1734
    @return: a copy of the passed in nicparams with missing keys filled
1735
        from the cluster defaults
1736

1737
    """
1738
    return FillDict(self.nicparams.get(constants.PP_DEFAULT, {}), nicparams)
1739

    
1740
  def SimpleFillOS(self, os_name, os_params):
1741
    """Fill an instance's osparams dict with cluster defaults.
1742

1743
    @type os_name: string
1744
    @param os_name: the OS name to use
1745
    @type os_params: dict
1746
    @param os_params: the dict to fill with default values
1747
    @rtype: dict
1748
    @return: a copy of the instance's osparams with missing keys filled from
1749
        the cluster defaults
1750

1751
    """
1752
    name_only = os_name.split("+", 1)[0]
1753
    # base OS
1754
    result = self.osparams.get(name_only, {})
1755
    # OS with variant
1756
    result = FillDict(result, self.osparams.get(os_name, {}))
1757
    # specified params
1758
    return FillDict(result, os_params)
1759

    
1760
  @staticmethod
1761
  def SimpleFillHvState(hv_state):
1762
    """Fill an hv_state sub dict with cluster defaults.
1763

1764
    """
1765
    return FillDict(constants.HVST_DEFAULTS, hv_state)
1766

    
1767
  @staticmethod
1768
  def SimpleFillDiskState(disk_state):
1769
    """Fill an disk_state sub dict with cluster defaults.
1770

1771
    """
1772
    return FillDict(constants.DS_DEFAULTS, disk_state)
1773

    
1774
  def FillND(self, node, nodegroup):
1775
    """Return filled out ndparams for L{objects.NodeGroup} and L{objects.Node}
1776

1777
    @type node: L{objects.Node}
1778
    @param node: A Node object to fill
1779
    @type nodegroup: L{objects.NodeGroup}
1780
    @param nodegroup: A Node object to fill
1781
    @return a copy of the node's ndparams with defaults filled
1782

1783
    """
1784
    return self.SimpleFillND(nodegroup.FillND(node))
1785

    
1786
  def SimpleFillND(self, ndparams):
1787
    """Fill a given ndparams dict with defaults.
1788

1789
    @type ndparams: dict
1790
    @param ndparams: the dict to fill
1791
    @rtype: dict
1792
    @return: a copy of the passed in ndparams with missing keys filled
1793
        from the cluster defaults
1794

1795
    """
1796
    return FillDict(self.ndparams, ndparams)
1797

    
1798
  def SimpleFillIPolicy(self, ipolicy):
1799
    """ Fill instance policy dict with defaults.
1800

1801
    @type ipolicy: dict
1802
    @param ipolicy: the dict to fill
1803
    @rtype: dict
1804
    @return: a copy of passed ipolicy with missing keys filled from
1805
      the cluster defaults
1806

1807
    """
1808
    return FillIPolicy(self.ipolicy, ipolicy)
1809

    
1810

    
1811
class BlockDevStatus(ConfigObject):
1812
  """Config object representing the status of a block device."""
1813
  __slots__ = [
1814
    "dev_path",
1815
    "major",
1816
    "minor",
1817
    "sync_percent",
1818
    "estimated_time",
1819
    "is_degraded",
1820
    "ldisk_status",
1821
    ]
1822

    
1823

    
1824
class ImportExportStatus(ConfigObject):
1825
  """Config object representing the status of an import or export."""
1826
  __slots__ = [
1827
    "recent_output",
1828
    "listen_port",
1829
    "connected",
1830
    "progress_mbytes",
1831
    "progress_throughput",
1832
    "progress_eta",
1833
    "progress_percent",
1834
    "exit_status",
1835
    "error_message",
1836
    ] + _TIMESTAMPS
1837

    
1838

    
1839
class ImportExportOptions(ConfigObject):
1840
  """Options for import/export daemon
1841

1842
  @ivar key_name: X509 key name (None for cluster certificate)
1843
  @ivar ca_pem: Remote peer CA in PEM format (None for cluster certificate)
1844
  @ivar compress: Compression method (one of L{constants.IEC_ALL})
1845
  @ivar magic: Used to ensure the connection goes to the right disk
1846
  @ivar ipv6: Whether to use IPv6
1847
  @ivar connect_timeout: Number of seconds for establishing connection
1848

1849
  """
1850
  __slots__ = [
1851
    "key_name",
1852
    "ca_pem",
1853
    "compress",
1854
    "magic",
1855
    "ipv6",
1856
    "connect_timeout",
1857
    ]
1858

    
1859

    
1860
class ConfdRequest(ConfigObject):
1861
  """Object holding a confd request.
1862

1863
  @ivar protocol: confd protocol version
1864
  @ivar type: confd query type
1865
  @ivar query: query request
1866
  @ivar rsalt: requested reply salt
1867

1868
  """
1869
  __slots__ = [
1870
    "protocol",
1871
    "type",
1872
    "query",
1873
    "rsalt",
1874
    ]
1875

    
1876

    
1877
class ConfdReply(ConfigObject):
1878
  """Object holding a confd reply.
1879

1880
  @ivar protocol: confd protocol version
1881
  @ivar status: reply status code (ok, error)
1882
  @ivar answer: confd query reply
1883
  @ivar serial: configuration serial number
1884

1885
  """
1886
  __slots__ = [
1887
    "protocol",
1888
    "status",
1889
    "answer",
1890
    "serial",
1891
    ]
1892

    
1893

    
1894
class QueryFieldDefinition(ConfigObject):
1895
  """Object holding a query field definition.
1896

1897
  @ivar name: Field name
1898
  @ivar title: Human-readable title
1899
  @ivar kind: Field type
1900
  @ivar doc: Human-readable description
1901

1902
  """
1903
  __slots__ = [
1904
    "name",
1905
    "title",
1906
    "kind",
1907
    "doc",
1908
    ]
1909

    
1910

    
1911
class _QueryResponseBase(ConfigObject):
1912
  __slots__ = [
1913
    "fields",
1914
    ]
1915

    
1916
  def ToDict(self):
1917
    """Custom function for serializing.
1918

1919
    """
1920
    mydict = super(_QueryResponseBase, self).ToDict()
1921
    mydict["fields"] = self._ContainerToDicts(mydict["fields"])
1922
    return mydict
1923

    
1924
  @classmethod
1925
  def FromDict(cls, val):
1926
    """Custom function for de-serializing.
1927

1928
    """
1929
    obj = super(_QueryResponseBase, cls).FromDict(val)
1930
    obj.fields = cls._ContainerFromDicts(obj.fields, list, QueryFieldDefinition)
1931
    return obj
1932

    
1933

    
1934
class QueryResponse(_QueryResponseBase):
1935
  """Object holding the response to a query.
1936

1937
  @ivar fields: List of L{QueryFieldDefinition} objects
1938
  @ivar data: Requested data
1939

1940
  """
1941
  __slots__ = [
1942
    "data",
1943
    ]
1944

    
1945

    
1946
class QueryFieldsRequest(ConfigObject):
1947
  """Object holding a request for querying available fields.
1948

1949
  """
1950
  __slots__ = [
1951
    "what",
1952
    "fields",
1953
    ]
1954

    
1955

    
1956
class QueryFieldsResponse(_QueryResponseBase):
1957
  """Object holding the response to a query for fields.
1958

1959
  @ivar fields: List of L{QueryFieldDefinition} objects
1960

1961
  """
1962
  __slots__ = []
1963

    
1964

    
1965
class MigrationStatus(ConfigObject):
1966
  """Object holding the status of a migration.
1967

1968
  """
1969
  __slots__ = [
1970
    "status",
1971
    "transferred_ram",
1972
    "total_ram",
1973
    ]
1974

    
1975

    
1976
class InstanceConsole(ConfigObject):
1977
  """Object describing how to access the console of an instance.
1978

1979
  """
1980
  __slots__ = [
1981
    "instance",
1982
    "kind",
1983
    "message",
1984
    "host",
1985
    "port",
1986
    "user",
1987
    "command",
1988
    "display",
1989
    ]
1990

    
1991
  def Validate(self):
1992
    """Validates contents of this object.
1993

1994
    """
1995
    assert self.kind in constants.CONS_ALL, "Unknown console type"
1996
    assert self.instance, "Missing instance name"
1997
    assert self.message or self.kind in [constants.CONS_SSH,
1998
                                         constants.CONS_SPICE,
1999
                                         constants.CONS_VNC]
2000
    assert self.host or self.kind == constants.CONS_MESSAGE
2001
    assert self.port or self.kind in [constants.CONS_MESSAGE,
2002
                                      constants.CONS_SSH]
2003
    assert self.user or self.kind in [constants.CONS_MESSAGE,
2004
                                      constants.CONS_SPICE,
2005
                                      constants.CONS_VNC]
2006
    assert self.command or self.kind in [constants.CONS_MESSAGE,
2007
                                         constants.CONS_SPICE,
2008
                                         constants.CONS_VNC]
2009
    assert self.display or self.kind in [constants.CONS_MESSAGE,
2010
                                         constants.CONS_SPICE,
2011
                                         constants.CONS_SSH]
2012
    return True
2013

    
2014

    
2015
class SerializableConfigParser(ConfigParser.SafeConfigParser):
2016
  """Simple wrapper over ConfigParse that allows serialization.
2017

2018
  This class is basically ConfigParser.SafeConfigParser with two
2019
  additional methods that allow it to serialize/unserialize to/from a
2020
  buffer.
2021

2022
  """
2023
  def Dumps(self):
2024
    """Dump this instance and return the string representation."""
2025
    buf = StringIO()
2026
    self.write(buf)
2027
    return buf.getvalue()
2028

    
2029
  @classmethod
2030
  def Loads(cls, data):
2031
    """Load data from a string."""
2032
    buf = StringIO(data)
2033
    cfp = cls()
2034
    cfp.readfp(buf)
2035
    return cfp